In Mathematica there are a number of functions that return not only the final result or a single match, but all results. Such functions are named *List
. Exhibit:
For example, I want:
MapList[f, {1, 2, 3, 4}]
{{f[1], 2, 3, 4}, {1, f[2], 3, 4}, {1, 2, f[3], 4}, {1, 2, 3, f[4]}}
I want a list element for each application of the function:
MapList[
f,
{h[1, 2], {4, Sin[x]}},
{2}
] // Column
{h[f[1], 2], {4, Sin[x]}} {h[1, f[2]], {4, Sin[x]}} {h[1, 2], {f[4], Sin[x]}} {h[1, 2], {4, f[Sin[x]]}}
MapList[f_, expr_, level_: 1] :=
MapAt[f, expr, #] & /@
Position[expr, _, level, Heads -> False]
a = Range@1000;
#^2 & /@ a // timeAvg
MapList[#^2 &, a] // timeAvg
ConstantArray[#^2 & /@ a, 1000] // timeAvg
0.00005088
0.01436
0.0003744
This illustrates that on average MapList
is about 38X slower than the combined total of mapping the function to every element in the list and creating a 1000x1000 array.
Mapping functions are a group of functions that could be applied successively to one or more lists of elements. The results of applying these functions to a list are placed in a new list and that new list is returned. For example, the mapcar function processes successive elements of one or more lists.
In Common Lisp apply is a function that applies a function to a list of arguments (note here that "+" is a variadic function that takes any number of arguments): (apply #'+ (list 1 2)) Similarly in Scheme: (apply + (list 1 2))
Description: reduce uses a binary operation, function, to combine the elements of sequence bounded by start and end. The function must accept as arguments two elements of sequence or the results from combining those elements. The function must also be able to accept no arguments.
I suspect that MapList
is nearing the performance limit for any transformation that performs structural modification. The existing target benchmarks are not really fair comparisons. The Map
example is creating a simple vector of integers. The ConstantArray
example is creating a simple vector of shared references to the same list. MapList
shows poorly against these examples because it is creating a vector where each element is a freshly generated, unshared, data structure.
I have added two more benchmarks below. In both cases each element of the result is a packed array. The Array
case generates new elements by performing Listable
addition on a
. The Module
case generates new elements by replacing a single value in a copy of a
. These results are as follows:
In[8]:= a = Range@1000;
#^2 & /@ a // timeAvg
MapList[#^2 &, a] // timeAvg
ConstantArray[#^2 & /@ a, 1000] // timeAvg
Array[a+# &, 1000] // timeAvg
Module[{c}, Table[c = a; c[[i]] = c[[i]]^2; c, {i, 1000}]] // timeAvg
Out[9]= 0.0005504
Out[10]= 0.0966
Out[11]= 0.003624
Out[12]= 0.0156
Out[13]= 0.02308
Note how the new benchmarks perform much more like MapList
and less like the Map
or ConstantArray
examples. This seems to show that there is not much scope for dramatically improving the performance of MapList
without some deep kernel magic. We can shave a bit of time from MapList
thus:
MapListWR4[f_, expr_, level_: {1}] :=
Module[{positions, replacements}
, positions = Position[expr, _, level, Heads -> False]
; replacements = # -> f[Extract[expr, #]] & /@ positions
; ReplacePart[expr, #] & /@ replacements
]
Which yields these timings:
In[15]:= a = Range@1000;
#^2 & /@ a // timeAvg
MapListWR4[#^2 &, a] // timeAvg
ConstantArray[#^2 & /@ a, 1000] // timeAvg
Array[a+# &, 1000] // timeAvg
Module[{c}, Table[c = a; c[[i]] = c[[i]]^2; c, {i, 1000}]] // timeAvg
Out[16]= 0.0005488
Out[17]= 0.04056
Out[18]= 0.003
Out[19]= 0.015
Out[20]= 0.02372
This comes within factor 2 of the Module
case and I expect that further micro-optimizations can close the gap yet more. But it is with eager anticipation that I join you awaiting an answer that shows a further tenfold improvement.
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