什么鬼,我去参加聚会。这是我的版本:
Flatten/@Flatten[Thread/@Transpose@{#,Mean/@#[[All,All,2]]}&@GatherBy[e,First],1]
我猜应该够快了。
编辑
针对@Mr.Wizard 的批评(我的第一个解决方案是重新排序列表),并探索问题的高性能角落,这里有 2 个替代解决方案:
getMeans[e_] :=
Module[{temp = ConstantArray[0, Max[#[[All, 1, 1]]]]},
temp[[#[[All, 1, 1]]]] = Mean /@ #[[All, All, 2]];
List /@ temp[[e[[All, 1]]]]] &[GatherBy[e, First]];
getMeansSparse[e_] :=
Module[{temp = SparseArray[{Max[#[[All, 1, 1]]] -> 0}]},
temp[[#[[All, 1, 1]]]] = Mean /@ #[[All, All, 2]];
List /@ Normal@temp[[e[[All, 1]]]]] &[GatherBy[e, First]];
第一个是最快的,用内存换取速度,并且可以在键都是整数时应用,并且您的最大“键”值(在您的示例中为 2)不太大。第二种解决方案不受后一种限制,但速度较慢。这是一个大的对列表:
In[303]:=
tst = RandomSample[#, Length[#]] &@
Flatten[Map[Thread[{#, RandomInteger[{1, 100}, 300]}] &,
RandomSample[Range[1000], 500]], 1];
In[310]:= Length[tst]
Out[310]= 150000
In[311]:= tst[[;; 10]]
Out[311]= {{947, 52}, {597, 81}, {508, 20}, {891, 81}, {414, 47},
{849, 45}, {659, 69}, {841, 29}, {700, 98}, {858, 35}}
这里的key可以是1到1000,其中500个,每个key有300个随机数。现在,一些基准测试:
In[314]:= (res0 = getMeans[tst]); // Timing
Out[314]= {0.109, Null}
In[317]:= (res1 = getMeansSparse[tst]); // Timing
Out[317]= {0.219, Null}
In[318]:= (res2 = tst[[All, {1}]] /.
Reap[Sow[#2, #] & @@@ tst, _, # -> Mean@#2 &][[2]]); // Timing
Out[318]= {5.687, Null}
In[319]:= (res3 = tst[[All, {1}]] /.
Dispatch[
Reap[Sow[#2, #] & @@@ tst, _, # -> Mean@#2 &][[2]]]); // Timing
Out[319]= {0.391, Null}
In[320]:= res0 === res1 === res2 === res3
Out[320]= True
我们可以看到这里getMeans是最快的,getMeansSparse第二快,@Mr.Wizard的解法要慢一些,但只有当我们使用Dispatch时,否则慢很多。我的和@Mr.Wizard 的解决方案(使用 Dispatch)在精神上是相似的,速度差异是由于(稀疏)数组索引比哈希查找更有效。当然,只有当您的列表非常大时,所有这些才重要。
编辑 2
这里是 getMeans 的一个版本,它使用带有 C 目标的 Compile 并返回数值(而不是有理数)。它比getMeans 快两倍左右,是我最快的解决方案。
getMeansComp =
Compile[{{e, _Integer, 2}},
Module[{keys = e[[All, 1]], values = e[[All, 2]], sums = {0.} ,
lengths = {0}, , i = 1, means = {0.} , max = 0, key = -1 ,
len = Length[e]},
max = Max[keys];
sums = Table[0., {max}];
lengths = Table[0, {max}];
means = sums;
Do[key = keys[[i]];
sums[[key]] += values[[i]];
lengths[[key]]++, {i, len}];
means = sums/(lengths + (1 - Unitize[lengths]));
means[[keys]]], CompilationTarget -> "C", RuntimeOptions -> "Speed"]
getMeansC[e_] := List /@ getMeansComp[e];
代码1 - Unitize[lengths] 防止未使用的密钥被零除。我们需要一个单独的子列表中的每个数字,所以我们应该调用getMeansC,而不是直接调用getMeansComp。以下是一些测量结果:
In[180]:= (res1 = getMeans[tst]); // Timing
Out[180]= {0.11, Null}
In[181]:= (res2 = getMeansC[tst]); // Timing
Out[181]= {0.062, Null}
In[182]:= N@res1 == res2
Out[182]= True
这可能被认为是一个高度优化的数值解决方案。 @Mr.Wizard 的完全通用、简短和漂亮的解决方案只慢了大约 6-8 倍这一事实对于后一种通用简洁解决方案来说非常好,所以,除非你想挤出每一微秒,否则我会坚持使用@Mr.Wizard 的(使用Dispatch)。但重要的是要知道如何优化代码,以及可以优化到什么程度(您可以期待什么)。