Group Abstract Group Abstract

Message Boards Message Boards

1
|
6.5K Views
|
4 Replies
|
9 Total Likes
View groups...
Share
Share this post:

Use KeyValueMap over several levels?

Posted 9 years ago

I have data for rivers and their tributaries that looks like this:

{"Colorado R"->{"Bill Williams R" -> {"Santa Maria R", "Big Sandy R"},"Gila R" -> {"San Francisco R" -> {"Tularosa R"}, "San Pedro R", "Santa Cruz R", "Salt R" -> {"Black R", "Tonto C", "Verde R" -> {"Oak C"}}, "Agua Fria R", "Hassayampa R"}}}

It means that the Bill Williams River is a tributary of the Colorado River, and the Santa Maria River is a tributary of the Bill Williams River, etc.

I want to end up with a list of pairs showing the smaller rivers flowing into the larger ones, something like this:

{"Bill Williams R ? Colorado R","Santa Maria R ? Bill Williams R","Big Sandy R ? Bill Williams R",...}

I have tried to use KeyValueMap[], but it doesn't allow me to specify multiple levels. I know there must be an elegant way to do this. Can anyone point me in the right direction?

Thanks in advance,

Mark Greenberg

POSTED BY: Mark Greenberg
4 Replies

Hi Mark,

This is an excellent case to use Cases:

t={"Colorado R"->{"Bill Williams R"->{"Santa Maria R","Big Sandy R"},"Gila R"->{"San Francisco R"->{"Tularosa R"},"San Pedro R","Santa Cruz R","Salt R"->{"Black R","Tonto C","Verde R"->{"Oak C"}},"Agua Fria R","Hassayampa R"}}};

ClearAll[f]
f[x_, y_List] := Module[{y2},
  y2 = If[Head[#] === Rule, First[#], #] & /@ y;
  StringRiffle[#, " \[Rule] "] & /@ Thread[{y2, x}]
]
result = Join @@ Cases[t, (x_String -> y_List) :> f[x, y], \[Infinity]];
result // Column

Hope that works for you.

POSTED BY: Sander Huisman
Posted 9 years ago

Sanders solution is definitely cool! Once the result is available, Mathematica could be used to visualize the result:

grlst = Map[Apply[DirectedEdge, StringTrim[StringSplit[#, "->"]]] &, 
   result];
gr = Graph[grlst, VertexLabels -> Placed["Name", Center]]

These two commands generate a directed graph which gives a nice overview on the problem, the directed edges should show the flow direction (hopefully)

POSTED BY: Michael Helmle
Posted 9 years ago

Thanks for another great solution, Sander. It does exactly what I needed.

Though I can't yet come up with solutions like these on my own, I have progressed to the point, after 9 months or so working with the Wolfram Language, where these types of solutions no longer seem like magic to me. To help those who still are baffled by solutions that nest several functions (and to help myself better understand this particular solution), I will pull Sander's code apart and explain:

The first step is to identify all the places in the data where a string goes to a list.

Cases[t, (x_String -> y_List), \[Infinity]]

Infinity is the level specification so that Cases will find the String->List pattern no matter how deeply nested it is.

:> f[x, y]

The code above then changes each String->List result by applying f[] to it.

f[x_, y_List] :=

The function f[] is defined for two parameters, x which is any expression and y which has to be a list. Since we are sending String/List pairs into the function, this should work every time.

Module[{y2},

This means that y2 will stay a local variable inside the module, which, I think, prevents the function from using old values of y2.

y2 = If[Head[#] === Rule, First[#], #] & /@ y;

Remember that y is a list. This code says that for every list element in y, the element in the list y2 is either y or the left-hand-side of y if y is a rule.

Thread[{y2, x}]

Thread[{{a, b, c}, d}] has the output of {{a, d}, {b, d}, {c, d}}, so this function makes a pair {y,x} of each element in y2.

StringRiffle[#, " \[Rule] "]

And for each of those pairs {y,x}, change them into a string "y ? x";

So far we have a bunch of lists, so the code below turns them into one long list.

result = Join @@

And finally Sander added // Column for pretty output.

I am still not 100% clear on how the delayed rule works in the line

Cases[t, (x_String -> y_List) :> f[x, y]

If I have made any mistakes in the above explanation, someone please correct me, for my sake and the sake of any who might try to learn from what I have written.

Some day I hope to be able to visualize solutions in Wolfram Language functions. Until then, I'm having fun experimenting and learning.

Thanks again, Sander.

Mark Greenberg

POSTED BY: Updating Name

Actually the pattern inside Cases can be also a Rule (rather than a RuleDelayed): (you still need some braces because of the precedence and grouping properties of Rule/RuleDelayed see the documentation page or use the 'secret' function Precedence)

Cases[t, (x_String -> y_List) -> f[x, y], \[Infinity]]

However x and y are now not 'local' variables, so you have to make sure 'x' and 'y' are not set to something (i.e. they must be undefined). By using RuleDelayed you avoid that problem. 'x' can have a value outside of Cases...

Basically the right hand side (f[x,y]) gets evaluated before being applied (but returns the same if x and y are not set because our function f needs y to be a List which it is not; it is a symbol at that point). With RuleDelayed it is only evaluation when it is getting applied (so x and y are 'filled in' by the values matched by the pattern-matcher and then evaluated, instead of the 'other way around'). It's hard to explain by words without intermediate feedback...

POSTED BY: Sander Huisman
Reply to this discussion
Community posts can be styled and formatted using the Markdown syntax.
Reply Preview
Attachments
Remove
or Discard