Okay, this has been a bit of a headache. The short answer is that it is all working as expected. Now for too much detail to put into a comment.
To begin, the original question was a bit of a blind, though certainly not by intent. When one considers what has to happen in a match with an Orderless
function such as Times
, it seems quite plausible that this might be very slow. This is alluded to in Help > Wolfram Documentation refguide page for Orderless
, under "Possible Issues":
Pattern matching with orderless functions can lead to a large number of possible cases:
In[1]:= SetAttributes[h, Orderless];
In[2]:= ReplaceList[h[a, b, c], h[x_, y_, z_] :> {x, y, z}]
Out[2]= {{a, b, c}, {a, c, b}, {b, a, c}, {b, c, a}, {c, a, b}, {c, b, a}}
Nonetheless, it is quite clear from what others have posted that sometimes this match is fast and sometimes slow, seemingly on the same input. This confusing flip-flop is due to the fact that what is being tested for a match is changing, due to how Unique
works. So the first thing is to get a stable set of examples.
To get this consistent set of behaviors showing both the fast and slow evaluations, I use a different construction of the factors below. I do this for four different products, with the only difference in naming being the first letter of the factors.
n = 22;
expr1 = Product[ToExpression[StringJoin["a", ToString[i]]], {i, n}];
expr2 = Product[ToExpression[StringJoin["b", ToString[i]]], {i, n}];
expr3 = Product[ToExpression[StringJoin["c", ToString[i]]], {i, n}];
expr4 = Product[ToExpression[StringJoin["d", ToString[i]]], {i, n}];
rep = {x_ f[y_] /; FreeQ[x, y] -> 0};
AbsoluteTiming[expr1 /. rep;]
AbsoluteTiming[expr2 /. rep;]
AbsoluteTiming[expr3 /. rep;]
AbsoluteTiming[expr4 /. rep;]
(* Out[359]= {1.482855, Null}
Out[360]= {0.000011, Null}
Out[361]= {8.*10^-6, Null}
Out[362]= {2.291128, Null} *)
This is entirely replicable and consistent: if I repeat the full evaluation I get the same set of fast vs slow evaluations.
So why are some slow and others not? For this we go to one of the deeper corners of documentation, specifically,
tutorial/SomeNotesOnInternalImplementation
Here are two relevant items:
"Each expression contains a special form of hash code that is used both in pattern matching and evaluation."
"A form of hashing that takes account of blanks and other features of patterns is used in pattern matching."
The details of this are outside the scope here. But the upshot is that sometimes this mechanism allows for an early exit, by containing information that entirely rules out a (sub)match. This is why some examples are so fast; they avoid the combinatorial explosion that would otherwise be needed to handle all possible reorderings.
One last detail is that this only happens when dealing with a head that is both Flat
and Orderless
. The latter means all reorderings of the pattern are required, the former means we also have to consider subsequences in the thing being matched. A reference here is:
tutorial/FlatAndOrderlessFunctions
So the following is across the board fast.
n = 22;
ClearAttributes[g, {Flat, Orderless}]
SetAttributes[g, {Orderless}]
expr1 = Apply[g,
Table[ToExpression[StringJoin["a", ToString[i]]], {i, n}]];
expr2 = Apply[g,
Table[ToExpression[StringJoin["b", ToString[i]]], {i, n}]];
expr3 = Apply[g,
Table[ToExpression[StringJoin["c", ToString[i]]], {i, n}]];
expr4 = Apply[g,
Table[ToExpression[StringJoin["d", ToString[i]]], {i, n}]];
rep = {g[x_, f[y_]] /; FreeQ[x, y] -> 0};
AbsoluteTiming[expr1 /. rep;]
AbsoluteTiming[expr2 /. rep;]
AbsoluteTiming[expr3 /. rep;]
AbsoluteTiming[expr4 /. rep;]
(* Out[443]= {0.000026, Null}
Out[444]= {9.*10^-6, Null}
Out[445]= {8.*10^-6, Null}
Out[446]= {9.*10^-6, Null} *)
But this variant has the same behavior as the original example that used head of Times
.
n = 22;
ClearAttributes[g, {Flat, Orderless}]
SetAttributes[g, {Flat, Orderless}]
expr1 = Apply[g,
Table[ToExpression[StringJoin["a", ToString[i]]], {i, n}]];
expr2 = Apply[g,
Table[ToExpression[StringJoin["b", ToString[i]]], {i, n}]];
expr3 = Apply[g,
Table[ToExpression[StringJoin["c", ToString[i]]], {i, n}]];
expr4 = Apply[g,
Table[ToExpression[StringJoin["d", ToString[i]]], {i, n}]];
rep = {g[x_, f[y_]] /; FreeQ[x, y] -> 0};
AbsoluteTiming[expr1 /. rep;]
AbsoluteTiming[expr2 /. rep;]
AbsoluteTiming[expr3 /. rep;]
AbsoluteTiming[expr4 /. rep;]
(* Out[455]= {1.481003, Null}
Out[456]= {7.*10^-6, Null}
Out[457]= {7.*10^-6, Null}
Out[458]= {2.361662, Null} *)
Hoping all this is of some use.
MatchQ
try to match the entire expression, whereasReplaceAll
has to test every subexpression? $\endgroup$