“MapList”功能

时间:2011-10-30 11:04:23

标签: performance map wolfram-mathematica mathematica-7

Mathematica 中,有许多函数不仅返回最终结果或单个匹配,而且返回所有结果。这些函数名为*List。图表:

  • FoldList
  • NestList
  • ReplaceList
  • ComposeList

我缺少的东西是MapList函数。

例如,我想:

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]}}

我想为函数的每个应用程序提供一个列表元素:

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]

然而,这是非常低效的。考虑这个简单的情况,并比较这些timings

a = Range@1000;
#^2 & /@ a // timeAvg
MapList[#^2 &, a] // timeAvg
ConstantArray[#^2 & /@ a, 1000] // timeAvg

0.00005088

0.01436

0.0003744

这说明平均MapList比将函数映射到列表中的每个元素并创建1000x1000数组的总和慢约38倍。


因此,如何最有效地实施MapList?

3 个答案:

答案 0 :(得分:6)

我怀疑MapList接近执行结构修改的任何转换的性能限制。现有的目标基准并不是真正公平的比较。 Map示例正在创建一个简单的整数向量。 ConstantArray示例正在创建一个对同一列表的共享引用的简单向量。 MapList对这些示例表现不佳,因为它正在创建一个向量,其中每个元素都是一个新生成的非共享数据结构。

我在下面添加了两个基准。在这两种情况下,结果的每个元素都是一个打包数组。 Array案例通过在Listable上执行a添加来生成新元素。 Module案例通过替换a副本中的单个值来生成新元素。这些结果如下:

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

请注意新基准测试的表现更像MapList,而不像MapConstantArray示例。这似乎表明,没有一些深层内核魔法,没有太大的余地可以显着提高MapList的性能。我们可以从MapList那里节省一点时间:

MapListWR4[f_, expr_, level_: {1}] :=
  Module[{positions, replacements}
  , positions = Position[expr, _, level, Heads -> False]
  ; replacements = # -> f[Extract[expr, #]] & /@ positions
  ; ReplacePart[expr, #] & /@ replacements
  ]

产生这些时间:

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

这是Module案例的第2个因素,我希望进一步的微观优化可以缩小差距。但我热切期待与你一起等待一个显示进一步改善十倍的答案。

答案 1 :(得分:6)

更新了我的功能

我想我可以在WReach的尝试之上再提供2倍的提升。

Remove[MapListTelefunken];
MapListTelefunken[f_, dims_] :=
 With[{a = Range[dims], fun = f[[1]]},
  With[{replace = ({#, #} -> fun) & /@ a},
   ReplacePart[ConstantArray[a, {dims}], replace]
   ]
  ]

以下是我的机器上的时间(Sony Z笔记本电脑; i7,8GB RAM,Raid 0中的256 SSD):

a = Range@1000;
#^2 & /@ a; // timeAvg
MapList[#^2 &, a]; // timeAvg
MapListWR4[#^2 &, a]; // timeAvg
MapListTelefunken[#^2 &, 1000]; // timeAvg

0.0000296 (* just Mapping the function over a Range[1000] for baseline *)
0.0297 (* the original MapList proposed by Mr.Wizard *)
0.00936 (* MapListWR4 from WReach *)
0.00468 (* my attempt *)

答案 2 :(得分:3)

我认为你仍然需要创建1000x1000阵列,我认为没有比常数阵列更聪明的方法。更重要的是,您的示例可以通过以下定义得到更好的服务,尽管我承认它缺乏精确的级别。

MapList[f_, list_] := (Table[MapAt[f, #, i], {i, Length@#}] &)@list;

您自己定义中的罪魁祸首是Position[]调用,它会创建一个复杂的辅助结构。

提供更复杂的用例,更好地满足您的意图。