# How to ReplaceAll independent of depth?

I have a big list which in principle can be described as this more compact list :

list1 = {{e, {{a, 0.1}, {b, 0.3}, {c, 0.5}, {d, 0.7}},
f, {{a, 0.2}, {b, 0.4}, {c, 0.6}, {d, 0.8}}, g, h, i}};


I am interested in rewriting to the following :

{{e, {"abcd", 0.1, 0.3, 0.5, 0.7}, f, {"abcd", 0.2, 0.4, 0.6, 0.8}, g,
h, i}}


which works well when applying this simple rule:

list1 /. {{a, x1_}, {b, x2_}, {c, x3_}, {d, x4_}} -> {"abcd", x1, x2,
x3, x4}


However when the affected sublists {a,x1} to {d,x4} are not on the same depth, I have difficulties constructing a rule that works. So for instance:

list2 = {{e, {a, 0.1}, {b, 0.3}, {c, 0.5}, {d, 0.7},
f, {{a, 0.2}, {b, 0.4}, {c, 0.6}, {d, 0.8}}, g, h, i}}


does not give the desired result.

So my questions are: 1. Is there a way to ReplaceAll independent of depth? 2. Is there a more elegant method that does the job?

Level really has nothing to do with the problem you are experiencing; ReplaceAll already works equivalently at all levels, following a depth-first preorder traversal.

Instead your problem is that the section of the expression that you wish to replace is not self-contained in list2. In list1 it is complete:

{{a, 0.1}, {b, 0.3}, {c, 0.5}, {d, 0.7}}


In list2 it is a subsequence of a larger expression:

{e, {a, 0.1}, {b, 0.3}, {c, 0.5}, {d, 0.7}, f, . . .}


This is simply not the same structure and it is not matched by your pattern. I believe the simplest workaround is to add a BlankNullSequence to be beginning and end of your pattern:

rule =
{p___, {a, x1_}, {b, x2_}, {c, x3_}, {d, x4_}, q___} :>
{p, {"abcd", x1, x2, x3, x4}, q};


(Note the use of RuleDelayed which is necessary to localize pattern names and which should have been used in your original rule as well.)

And then use ReplaceRepeated to catch all cases:

list2 //. rule

{{e, {"abcd", 0.1, 0.3, 0.5, 0.7}, f, {{"abcd", 0.2, 0.4, 0.6, 0.8}}, g, h, i}}


This is however terribly inefficient as many different alignments must be tried and the expression is repeatedly scanned. I am thinking about a better alternative that is sufficiently flexible.

The only simple optimization that comes to mind is replace ReplaceRepeated with a manual application of /. on only the parts of the expression that have not already been scanned. This should greatly improve performance on long lists as it will prevent the entire expression from being rescanned for every replacement.

rule2 = {p___, {a, x1_}, {b, x2_}, {c, x3_}, {d, x4_}, q___} :>
Join[{p, {"abcd", x1, x2, x3, x4}}, {q} /. rule2];


Note the self-referential use of rule2; it is important to keep this Symbol defined for the replacement to work. Now:

list2 /. rule2

{{e, {"abcd", 0.1, 0.3, 0.5, 0.7}, f, {{"abcd", 0.2, 0.4, 0.6, 0.8}}, g, h, i}}

• Nice! I tried this approach myself but now see my mistake... I forgot to add a letter to the BlankNullSequence ___ . Darn, I was so close :) Aug 14 '14 at 17:48
• @MathLind This question/operation comes up so often I really think Mathematica needs a simpler syntax for it. I wish that this would work: list2 /. PatternSequence[{a, x1_}, {b, x2_}, {c, x3_}, {d, x4_}] :> {"abcd", x1, x2, x3, x4} (It of course doesn't.) Aug 14 '14 at 17:52
• Hear hear. But alas, it has good reasons not to work ... Aug 14 '14 at 18:51
• @TeakeNutma I missed that question; thanks for the link! Aug 14 '14 at 18:53
• @TeakeNutma What Tali wrote: ... when it really could be O(n), if we had better ways of describing and using sequence-based patterns. What we really need is one or two functions with the same semantics as the String* functions ... is exactly what I am talking about, though on reflection I agree that PatternSequence likely plays no role in that. I have actually converted expressions into strings simply for the sequence handling, which seems indicative of a hole in the language at present. Aug 14 '14 at 18:57
list2 = {{e, {a, 0.1}, {b, 0.3}, {c, 0.5}, {d, 0.7}, f, {{a, 0.2}, {b, 0.4}, {c, 0.6}, {d, 0.8}}, g, h, i}};

list2 /.
{{a, x1_}, {b, x2_}, {c, x3_}, {d, x4_}} :> {"abcd", x1, x2, x3, x4}


{{e, {a, 0.1}, {b, 0.3}, {c, 0.5}, {d, 0.7}, f, {"abcd", 0.2, 0.4, 0.6, 0.8}, g, h, i}}

• This works very well eldo. Let me metabolize your code a while. BTW how do you make your output yellow like above. Aug 14 '14 at 17:28
• @MathLind Thanks, but see my comment to Öska's answer :)
– eldo
Aug 14 '14 at 17:30
• @MathLind The yellow background is because he used the blockquote button in the editor instead of the code sample button. Aug 14 '14 at 17:46
• @Pickett Thanks, I will apply this in the future. Aug 14 '14 at 17:50

I tried another alternative using Replace with {0, Infinity} levelspec and it was surprisingly faster than ReplaceAll in the other answer.

replace = Replace[#, {p___, {a, x1_}, {b, x2_}, {c, x3_}, {d, x4_},
q___} -> {p, {"abcd", x1, x2, x3, x4}, q}, {0, Infinity}] &;
replace[list2]
(* {{e, {"abcd", 0.1, 0.3, 0.5, 0.7}, f, {{"abcd", 0.2, 0.4, 0.6, 0.8}},
g, h, i}} *)


Comparing methods

list = Nest[
Append[List /@ #,
Transpose@{{a, b, c, d}, RandomReal[1, 4]}~Prepend~
RandomChoice[CharacterRange["a", "z"]]] &, {}, 10]


Equal @@ (#@list & /@ {# //. rule &, # /. rule2 &, replace[#] &})
(* True *)


Timings

Module[{rule, rule2, replace, listCompare, timings},
rule = {p___, {a, x1_}, {b, x2_}, {c, x3_}, {d, x4_},
q___} :> {p, {"abcd", x1, x2, x3, x4}, q};
rule2 = {p___, {a, x1_}, {b, x2_}, {c, x3_}, {d, x4_}, q___} :>
Join[{p, {"abcd", x1, x2, x3, x4}}, {q} /. rule2];
replace =
Replace[#, {p___, {a, x1_}, {b, x2_}, {c, x3_}, {d, x4_},
q___} -> {p, {"abcd", x1, x2, x3, x4}, q}, {0, Infinity}] &;
listCompare = {# //. rule &, # /. rule2 &, replace[#] &};
timings[n_Integer] :=
With[{list =
Nest[Append[List /@ #,
Transpose@{{a, b, c, d}, RandomReal[1, 4]}~Prepend~
RandomChoice[CharacterRange["a", "z"]]] &, {}, n]},
{n, First@AbsoluteTiming@#@list} & /@ listCompare];
timings /@ Range[100, 1000, 100] // Transpose //
ListLinePlot[#, PlotLegends -> {"rule", "rule2", "replace"},
AxesLabel -> {"Levels", "AbsoluteTiming"}] &]


• I only noticed this answer now. Interesting claims! But I am too tired now to dig into it. Please remind me if I haven't responded in a few days. Oct 25 '14 at 9:37