I am still not good working with lists in Mathematica the functional way. Here is a small problem that I'd like to ask what is a good functional way to solve.
I have say the following list made up of points. Hence each element is coordinates (x,y) of one point.
a = {{1, 2}, {3, 4}, {5, 6}}
I'd like to traverse this list, and every time I find a point whose y-coordinate is say > 3.5, I want to generate a complex conjugate point of it. At the end, I want to return a list of the points generated. So, in the above example, there are 2 points which will meet this condition. Hence the final list will have 5 points in it, the 3 original ones, and 2 complex conjugtes ones.
I tried this:
If[#[[2]] > 3.5, {#, {#[[1]], -#[[2]]}}, #] & /@ a
but I get this
{{1, 2}, {{3, 4}, {3, -4}}, {{5, 6}, {5, -6}}}
You see the extra {} in the middle, around the points where I had to add a complex conjugate point. I'd like the result to be like this:
{{1, 2}, {3, 4}, {3, -4}, {5, 6}, {5, -6}}
I tried inserting Flatten, but did not work, So, I find myself sometimes going back to my old procedural way, and using things like Table and Do loop like this:
a = {{1, 2}, {3, 4}, {5, 6}}
result = {};
Do[
If[a[[i, 2]] > 3.5,
{
AppendTo[result, a[[i]]]; AppendTo[result, {a[[i, 1]], -a[[i, 2]]}]
},
AppendTo[result, a[[i]]]
],
{i, 1, Length[a]}
]
Which gives me what I want, but not functional solution, and I do not like it.
What would be the best functional way to solve such a list operation?
update 1
Using the same data above, let assume I want to make a calculation per each point as I traverse the list, and use this calculation in building the list. Let assume I want to find the Norm of the point (position vector), and use that to build a list, whose each element will now be {norm, point}. And follow the same logic as above. Hence, the only difference is that I am making an extra calculation at each step.
This is what I did using the solution provided:
a = {{1, 2}, {3, 4}, {5, 6}}
If[#[[2]] > 3.5,
Unevaluated@Sequence[ {Norm[#], #}, {Norm[#], {#[[1]], -#[[2]]}}],
{Norm[#], #}
] & /@ a
Which gives what I want:
{ {Sqrt[5],{1,2}}, {5,{3,4}}, {5,{3,-4}}, {Sqrt[61],{5,6}}, {Sqrt[61],{5,-6}} }
The only issue I have with this, is that I am duplicating the call to Norm[#] for the same point in 3 places. Is there a way to do this without this duplication of computation?
This is how I currently do the above, again, using my old procedural way:
a = {{1, 2}, {3, 4}, {5, 6}}
result = {};
Do[
o = Norm[a[[i]]];
If[a[[i, 2]] > 3.5,
{
AppendTo[result, {o, a[[i]]}]; AppendTo[result, {o, {a[[i, 1]], -a[[i, 2]]}}]
},
AppendTo[result, {o, a[[i]]}]
],
{i, 1, Length[a]}
]
And I get the same result as the functional way, but in the above, since I used a temporary variable, I am doing the calculation one time per point.
Is this a place for things like sow and reap? I really never understood well these 2 functions. If not, how would you do this in functional way?
thanks
One way is to use Sequence
.
Just a minor modification to your solution:
If[#1[[2]] > 3.5, Unevaluated@Sequence[#1, {#1[[1]], -#1[[2]]}], #1] & /@ a
However, a plain ReplaceAll
might be simpler:
a /. {x_, y_} /; y > 3.5 :> Sequence[{x, y}, {x, -y}]
This type of usage is the precise reason Rule
and RuleDelayed
have attribute SequenceHold
.
Answer to update 1
I'd do it in two steps:
b = a /. {x_, y_} /; y > 3.5 :> Sequence[{x, y}, {x, -y}]
{Norm[#], #}& /@ b
In a real calculation there's a chance you'd want to use the norm separately, so a Norm /@ b
might do
While Mathematica can simulate functional programming paradigms quite well, you might consider using Mathematica's native paradigm -- pattern matching:
a = {{1,2},{3,4},{5,6}}
b = a /. p:{x_, y_ /; y > 3.5} :> Sequence[p, {x, -y}]
You can then further transform the result to include the Norm
s:
c = Cases[b, p_ :> {Norm@p, p}]
There is no doubt that using Sequence
to generate a very large list is not as efficient as, say, pre-allocating an array of the correct size and then updating it using element assignments. However, I usually prefer clarity of expression over such micro-optimization unless said optimization is measured to be crucial to my application.
Flatten
takens a second argument that specifies the depth to which to flatten. Thus, you could also do the following.
a = {{1, 2}, {3, 4}, {5, 6}};
Flatten[If[#[[2]] > 3.5, {#, {#[[1]], -#[[2]]}}, {#}] & /@ a, 1]
The most serious problem with your Do
loop is the use of AppendTo
. This will be very slow if result
grows long. The standard way to deal with lists that grow as the result of a procedure like this is to use Reap
and Sow
. In this example, you can do something like so.
new = Reap[
Do[If[el[[2]] > 3.5, Sow[{el[[1]], -el[[2]]}]],
{el, a}]][[2, 1]];
Join[a, new]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With