Solutions with Inner
Here is the function I came up with
ClearAll[toExprPosLists];
SetAttributes[toExprPosLists, HoldAllComplete]
toExprPosLists[expr_] :=
Inner @@
Function[
Hold@
Evaluate[
List,
Unevaluated @@
Level[Unevaluated@expr, {0, Infinity}, #, Heads -> True],
Position[Unevaluated@expr, _],
#
]
]@Function[Null, HoldComplete[List@##], HoldAllComplete]
We then have
head = 6;
{
# (*timing*),
{
(HoldComplete @@@ #2)[[1, All]] // Length,
Position[tree12HC, _] // Length,
2^12 + 2^11 + 2
} (*number of subexpressions*)
,
#2[[All, ;; 5]] (*sample of subexpressions*)
} & @@
(toExprPosLists @@ List@tree12HC // Timing)
Clear[head]
{
0.015144, (timing)
{6146, 6146, 6146}, (number of subexpressions)
HoldComplete[{{HoldComplete, {0}}, {head, {1, 0}}, {head, {1, 1,
0}}, {head, {1, 1, 1, 0}}, {head, {1, 1, 1, 1, 0}}}] (sample of subexpressions)
}
Remarks
All the HoldComplete
wrappers and the HoldAllComplete
attributes serve to minimize overhead. To see what I mean, consider the difference between
head = List;
Trace[Hold @@ tree12HC, TraceOriginal -> True] // LeafCount
Clear[head]
16411
head = List;
Trace[tree12HC, TraceOriginal -> True] // LeafCount
Clear[head]
4101
I am not sure if I have succeeded in stopping all "rebuilding of expressions". But at least I have kept such considerations in mind while making the function :).
"Less convoluted"
Note that we could also have written
ClearAll[toExprPosLists3];
toExprPosLists3[expr_] :=
Inner[
List,
Level[Unevaluated@expr, {0, Infinity}, HoldComplete, Heads -> True],
HoldComplete@@Position[Unevaluated@expr, _],
HoldComplete
]
But this appears to be slower. Also this does not look much more convoluted than Mr.Wizards answer, it only uses Inner
instead of Thread
. However, it is slower than the function I defined above. I think I understand a little bit why this function is slower, that is why I came up with the "convoluted version" in the first place :). Below is an explanation.
About the optimization
All of this has to do with the fact that it takes a bit of time to do something like HoldComplete@@expressionWithManyArguments
. That is also the reason why I let the function return something of the form HoldComplete[List[___]]
rather than HoldComplete[___]
, because it is more likely that you will need a list than something with head HoldComplete. This allows you to prevent one more List@@expressionWithManyArguments
. Also in case you do var = expressionWithManyArguments
and then do List@@var
, you are using twice as much memory as necessary.
However, all of that optimization/"convolution" is minor compared to the difference in performance between Inner and Thread, it seems.
Showing that Inner is bad
To take this argument that Inner is worse than Thread to the extreme, let's make our own version of Inner (that will only work correctly with a very specific subset of possible arguments). We could set
myInner[head1_, exprs__, head2_] :=
head1 @@@ Thread[{exprs}, head2]
and
ClearAll[toExprPosLists4];
SetAttributes[toExprPosLists4, HoldAllComplete]
toExprPosLists4[expr_] :=
myInner[
List,
Level[Unevaluated@expr, {0, Infinity}, HoldComplete,
Heads -> True],
HoldComplete @@ Position[Unevaluated@expr, _],
HoldComplete
]
Which turns out to be faster, because as it turns out Inner is just not suitable for the job.
Comparison of timings
toExprPosLists @@ tree12HC // Timing // First
toExprPosLists3[tree12] // Timing // First
toExprPosLists4 @@ tree12HC // Timing // First
index2[tree12, {0, Infinity}] // Timing // First
0.015313
0.016765
0.010508
0.006448
Remarks about Mr.Wizards answer
So, Thread
seems to be the best option for this (I think it is not possible to use MapThread
, though maybe we can use InheritedBlock). I think there is no optimization to be done w.r.t avoiding apply, because of the stringent syntax of Thread
. In my opinion, it is a bit of a shame we end up with something matching HoldComplete[___]
rather than HoldComplete[{___}]
, but I do not see a good way around this.