7
$\begingroup$

In an answer to this question, rm -rf used the function Replace with level spefication to do a series of replacements inside out. As far as I can see, the order of substitutions when more than one level is involved is not documented, so I studied this a little bit deeper and constructed the following example for flattening a list, with an administration of the sublists that are replaced.

expr = {1, {2, {4, {5, 6}, {7, 8}}}};
usedsublists = {};
Replace[expr, x : {__} :> (AppendTo[usedsublists, x ]; Sequence @@ x), Infinity] 
usedsublists

(*  {1,2,4,5,6,7,8}  *)

(*  {{5,6},{7,8},{4,5,6,7,8},{5,6},{7,8},{2,4,5,6,7,8},{5,6},{7,8},{4,5,6,7,8},{5,6},{7,8}}  *)

Indeed, Replace works inside out. But it seems that Replace repeats substitutions it has already done for some reason. Do examples exist where this duplication is necessary, or is it a slight inefficiency in the implementation?

$\endgroup$
  • 2
    $\begingroup$ Replace does a depth-first postorder traversal of the expression tree. See this very interesting thread for a discussion of the topic. $\endgroup$ – sebhofer Dec 3 '14 at 10:46
  • $\begingroup$ @sebhofer Many thanks for this reference, indeed very interesing and completely new for me. It does not really answer my question, but the problem is very likely not in Replace, though I have no idea where else. See the answer I just posted. $\endgroup$ – Fred Simons Dec 3 '14 at 14:54
4
$\begingroup$

To understand how you end up with a length of 11 for your usedsubsets you must consider not only the traversal order(1)(2)(3) in which the expression is scanned for pattern matching but the evaluation order as well. Much like Map as I summarized in Scan vs. Map vs. Apply Replace does not perform a sequential evaluation* but performs all replacements and then evaluates the entire expression.

If we change the head of your expr to Hold we not only witness the behavior described above but also clear illustration of the source of 11:

expr = Hold[1, {2, {4, {5, 6}, {7, 8}}}];

usedsubsets = {};
Replace[expr, x : {__} :> (AppendTo[usedsubsets, x]; Sequence @@ x), Infinity]
Hold[1, AppendTo[
  usedsubsets, {2, 
   AppendTo[usedsubsets, {4, AppendTo[usedsubsets, {5, 6}]; Sequence @@ {5, 6}, 
     AppendTo[usedsubsets, {7, 8}]; Sequence @@ {7, 8}}]; 
   Sequence @@ {4, AppendTo[usedsubsets, {5, 6}]; Sequence @@ {5, 6}, 
     AppendTo[usedsubsets, {7, 8}]; Sequence @@ {7, 8}}}]; 
 Sequence @@ {2, 
   AppendTo[usedsubsets, {4, AppendTo[usedsubsets, {5, 6}]; Sequence @@ {5, 6}, 
     AppendTo[usedsubsets, {7, 8}]; Sequence @@ {7, 8}}]; 
   Sequence @@ {4, AppendTo[usedsubsets, {5, 6}]; Sequence @@ {5, 6}, 
     AppendTo[usedsubsets, {7, 8}]; Sequence @@ {7, 8}}}]

Lest you think this has anything to do with AppendTo, Sequence, or even CompoundExpression or Hold, here is another more generic example:

Replace[List @@ expr, x : {__} :> f[g[x], h[x]], Infinity]
{1, f[g[{2, 
    f[g[{4, f[g[{5, 6}], h[{5, 6}]], f[g[{7, 8}], h[{7, 8}]]}], 
     h[{4, f[g[{5, 6}], h[{5, 6}]], f[g[{7, 8}], h[{7, 8}]]}]]}], 
  h[{2, f[g[{4, f[g[{5, 6}], h[{5, 6}]], f[g[{7, 8}], h[{7, 8}]]}], 
     h[{4, f[g[{5, 6}], h[{5, 6}]], f[g[{7, 8}], h[{7, 8}]]}]]}]]}

This is merely the result of the "inside-out" replacement you referenced in your question.

*The exception to this statement is the use of either RuleCondition or its more verbose cousin, the Trott-Strzebonski in-place evaluation technique. These cause early evaluation of the RHS of the rules, after pattern substitution but before replacement in the main expression.

$\endgroup$
2
$\begingroup$

With thanks to @sebhofer for his reference, now I can mostly, but not fully, answer my own question. In his reference I found some highly elegant tricks, used by @Mr.Wizard and much better than mine, to monitor Replace. Using these tricks, we see that Replace successive visits the following subexpressions (without applying the substitutions):

Replace[expr, x:{__}/;(Print[x];False):>Sequence@@x,Infinity];
(* {5,6}
{7,8}
{4,{5,6},{7,8}}
{2,{4,{5,6},{7,8}}} *)

and that during this search, it immediately replaces:

Replace[expr, x:{__}/;(Print[x];True):>Sequence@@x,Infinity];

(* {5,6}
{7,8}
{4,5,6,7,8}
{2,4,5,6,7,8} *)

So Replace is not doing double work. What I observed in my question must have a different cause. In the following form everything is fine:

count=0;
Replace[expr, x:{__}:>(count=count+1; Sequence@@x),Infinity];
count

(* 4 *)

But when I keep track on the lists on which the rule is applied, it goes wrong:

count=0;
usedsubsets={};
Replace[expr, x:{__}:>(count=count+1; AppendTo[usedsubsets, x];Sequence@@x),Infinity];
count
Length[usedsubsets]

(* 11 *)
(* 11 *)

The number of substitutions increased from 4 to 11. I fail to see any explanation.

$\endgroup$
  • $\begingroup$ Strange indeed. You also can use Reap and Sow instead of Printto track what is going on. I get even more strange results with this : Reap@Replace[expr, x : {__} :> (count = count + 1; usedsubsets = Flatten[{usedsubsets, {x}}, 1];Sequence @@ Sow[x]), Infinity] or Reap@Replace[expr, x : {__} :> (count = count + 1; usedsubsets = Join[usedsubsets, {x}]; Sequence @@ Sow[x]), Infinity]. Now count is 11 and Length@usedubsets is 4 ! $\endgroup$ – SquareOne Dec 3 '14 at 15:28
  • 1
    $\begingroup$ Totally blind guess and probably wrong, but I could imagine this is connected to how Mathematica determines if an expression changed (i.e., when to stop computing)... $\endgroup$ – sebhofer Dec 3 '14 at 18:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.