【问题标题】:Delete repeating list elements preserving order of appearance删除保留出现顺序的重复列表元素
【发布时间】:2011-03-09 13:17:55
【问题描述】:

我正在制作包含 10^6 到 10^7 实数的平面列表,其中一些是重复的。

我需要删除重复的实例,只保留第一次出现,并且不修改列表顺序。

这里的关键是效率,因为我有很多列表要处理。

例子(假的):

输入:

  {.8, .3 , .8, .5, .3, .6}

期望的输出

  {.8, .3, .5, .6}  

旁注

使用 Union 删除重复元素(不保留顺序)会出现在我可怜的笔记本电脑中:

DiscretePlot[a = RandomReal[10, i]; First@Timing@Union@a, {i, 10^6 Range@10}]

【问题讨论】:

    标签: wolfram-mathematica


    【解决方案1】:

    你想要DeleteDuplicates,它会保留列表顺序:

    In[13]:= DeleteDuplicates[{.8, .3, .8, .5, .3, .6}]
    
    Out[13]= {0.8, 0.3, 0.5, 0.6}
    

    它是在 Mathematica 7.0 中添加的。

    【讨论】:

    • @Michael:我宁愿使用DeleteDuplicates[...,Equal],因为有问题的数字是真实的,默认比较是SameQ。慢得多,但更健壮。
    • 嗯,我总是忘记像 DeleteDuplicates 这样的内置插件。对于它的价值,在我的回答中,作为一个内置的在 ~10^6 元素上的运行速度比 unsortedUnion 快 3 - 6 倍。 (注意,这里使用的是SameQ 而不是Equal。)
    • @Leonid 可以先使用Round 的数字,然后使用小于$MachinePrecision 的数字,然后使用普通的DeleteDuplicates,因为DeleteDuplicates[...,Equal] 非常慢,而belisarius 想要速度?
    • @Mr.Wizard:这可能有效,但不确定您的建议是否足够强大,我一般不会依赖SameQ 来处理数字。但是,正如@belisarius 所说,就他的目的而言,SameQ 似乎很好。 @belisarius 好的,那么 DeleteDuplicates 确实是你的朋友。另一个(强大但较慢)替代方案是Tally[list, Equal][[All, 1]]。关于Union 显式测试很慢,这主要是因为它切换到二次时间算法,例如看到这个线程:groups.google.com/group/comp.soft-sys.math.mathematica/…
    • @belisarius,刚刚检查了 Union 的时间,它的运行速度大约是这段代码的两倍。因此,对于我的解决方案的实用性非常重要。删除它,因为它无法竞争。
    【解决方案2】:

    不与其他答案竞争,但我忍不住分享基于Compile 的解决方案。该解决方案基于构建二叉搜索树,然后检查列表中的每个数字,是否它在列表中的索引是用于构建 b 树的索引。如果是,它是原始号码,如果不是 - 它是重复的。这个解决方案对我来说很有趣的原因在于它展示了一种使用Compile 模拟“通过引用”的方法。关键是,如果我们将编译函数内联到其他编译函数中(这可以通过“InlineCompiledFunctions”选项实现),我们可以在内部函数中引用外部函数作用域中定义的变量(因为内联的工作方式) .这不是真正的按引用传递,但它仍然允许组合来自较小块的函数,而不会降低效率(这更符合宏扩展的精神)。我不认为这是记录在案的,也不知道这是否会保留在未来的版本中。无论如何,这是代码:

    (* A function to build a binary tree *)
    Block[{leftchildren , rightchildren},
    makeBSearchTree = 
    Compile[{{lst, _Real, 1}},
    Module[{len = Length[lst], ctr = 1, currentRoot = 1},
     leftchildren = rightchildren =  Table[0, {Length[lst]}];
     For[ctr = 1, ctr <= len, ctr++,
      For[currentRoot = 1, lst[[ctr]] != lst[[currentRoot]],(* 
       nothing *),
       If[lst[[ctr]] < lst[[currentRoot]],
        If[leftchildren[[currentRoot]] == 0,
         leftchildren[[currentRoot]] = ctr;
         Break[],
         (* else *)
         currentRoot = leftchildren[[currentRoot]] ],
        (* else *)
        If[rightchildren[[currentRoot]] == 0,
         rightchildren[[currentRoot]] = ctr;
         Break[],
         (* else *)
         currentRoot = rightchildren[[currentRoot]]]]]];
     ], {{leftchildren, _Integer, 1}, {rightchildren, _Integer, 1}},
    CompilationTarget -> "C", "RuntimeOptions" -> "Speed",
    CompilationOptions -> {"ExpressionOptimization" -> True}]];
    
    
    (* A function to query the binary tree and check for a duplicate *)
    Block[{leftchildren , rightchildren, lst},
    isDuplicate = 
    Compile[{{index, _Integer}},
    Module[{currentRoot = 1, result = True},
     While[True,
      Which[
       lst[[index]] == lst[[currentRoot]],
        result = index != currentRoot;
        Break[],
       lst[[index]] < lst[[currentRoot]],
        currentRoot = leftchildren[[currentRoot]],
       True,
        currentRoot = rightchildren[[currentRoot]]
       ]];
     result
     ],
    {{leftchildren, _Integer, 1}, {rightchildren, _Integer, 
      1}, {lst, _Real, 1}},
    CompilationTarget -> "C", "RuntimeOptions" -> "Speed",
    CompilationOptions -> {"ExpressionOptimization" -> True}
    ]];
    
    
    (* The main function *)
    Clear[deldup];
    deldup = 
    Compile[{{lst, _Real, 1}},
      Module[{len = Length[lst], leftchildren , rightchildren , 
         nodup = Table[0., {Length[lst]}], ndctr = 0, ctr = 1},
    makeBSearchTree[lst]; 
    For[ctr = 1, ctr <= len, ctr++,
     If[! isDuplicate [ctr],
      ++ndctr;
       nodup[[ndctr]] =  lst[[ctr]]
      ]];
    Take[nodup, ndctr]], CompilationTarget -> "C", 
    "RuntimeOptions" -> "Speed",
    CompilationOptions -> {"ExpressionOptimization" -> True,
     "InlineCompiledFunctions" -> True, 
     "InlineExternalDefinitions" -> True}];
    

    这里有一些测试:

    In[61]:= intTst = N@RandomInteger[{0,500000},1000000];
    
    In[62]:= (res1 = deldup[intTst ])//Short//Timing
    Out[62]= {1.141,{260172.,421188.,487754.,259397.,<<432546>>,154340.,295707.,197588.,119996.}}
    
    In[63]:= (res2 = Tally[intTst,Equal][[All,1]])//Short//Timing
    Out[63]= {0.64,{260172.,421188.,487754.,259397.,<<432546>>,154340.,295707.,197588.,119996.}}
    
    In[64]:= res1==res2
    Out[64]= True
    

    没有Tally 版本快,但也基于Equal,正如我所说,我的意思是说明一种有趣的 (IMO) 技术。

    【讨论】:

    • 我不明白你写的一半,所以我必须相信你知道你在做什么,但听起来很酷,所以 +1。总有一天,我将不得不学习一门低级语言,然后也许我会明白。
    • @Leonid +1 感谢您分享这些想法。我相信我会学到很多东西
    • @Leonid 这是一种有趣的技术,对Compile 的使用有点不合常规。如果您的已编译代码受到从已编译代码回调 Mathematica 的阻碍,则性能会受到影响。您可以通过加载 Needs["CompiledFunctionTools"](注意:使用反引号)并评估 CompilePrint[makeBSearchTree] 来查看它们。您将看到 MainEvaluate 的出现,这意味着对 Mathematica 的回调。
    • @Sasha:起初,我被你的观察弄糊涂了。但随后,我查看了deldup 的最终代码(通过CompilePrint),并找到了我所希望的(即我从我对该技术的有限经验中直观地期望的):编译器足够聪明,可以删除从被内联到另一个编译函数的函数中对MainEvaluate 的调用!所以,如果这里效率低下,我认为这不是因为调用了 mma 评估器。辅助功能的状态是积木,不能单独使用。
    • @Leonid 是的,你是对的!在这种情况下,可以将CompilationTarget-&gt;"C" 放入那些辅助功能中。 Compile 在构建 deldup 时将仅使用他们的 Mathematica 表示。从编译后的打印结果来看,代码是尽可能高效的,所以我认为Tally 只是使用了更高效的算法,再加上更好的优化代码,我猜。
    【解决方案3】:

    对于 Mathematica 7 之前的版本,出于一般兴趣,这里有几种实现 UnsortedUnion(即 DeleteDuplicates)函数的方法。这些是从帮助文档和 MathGroup 中收集的。它们已被调整为接受多个列表,然后加入,类似于 Union。

    对于 Mathematica 4 或更早版本

    UnsortedUnion = Module[{f}, f[y_] := (f[y] = Sequence[]; y); f /@ Join@##] &
    

    对于 Mathematica 5

    UnsortedUnion[x__List] := Reap[Sow[1, Join@x], _, # &][[2]]
    

    对于 Mathematica 6

    UnsortedUnion[x__List] := Tally[Join@x][[All, 1]]
    

    来自 Mathematica 3+ 的 Leonid Shifrin (?)

    unsortedUnion[x_List] := Extract[x, Sort[Union[x] /. Dispatch[MapIndexed[Rule, x]]]]
    

    【讨论】:

    • @Mr.而且这里没有“考古学家徽章”! +1
    • 虽然它可能并不重要,但 Ted Ersek 指出 UnsortedUnion 版本 1 将失败,列表如 {3, 1, 1, f[3], 3, 2, f [3]、1、1、8、2、6、8}。 (他讨论了这个函数的起源,{最初归功于 Carl Woll?}here,在“聪明的小程序”下他的版本:ClearAll[DeleteRepititions]; DeleteRepititions[x_]:= Module[{t}, t[n_ ]:= (t[n] = Sequence[]; n); t/@x])
    • @TomD 抓得好。 Block改为Module
    • @Mr.Wizard 这是另一个要添加到您的收藏中的:):unsortedUnion[x_List] := Extract[x, Sort[Union[x] /. Dispatch[MapIndexed[Rule, x]]]]。从我认为 v.4.2 开始工作(当 Dispatch 被引入时)。对于大型列表,大约比 Reap-Sow 版本快 2-3 倍。我在这里的书中讨论它:mathprogramming-intro.org/book/node479.html
    • @Leonid 谢谢。我还没有读过那部分。我相信在完成你的书之前我会学到很多东西。然后我将等待第 2 版。
    猜你喜欢
    • 1970-01-01
    • 2017-11-23
    • 2013-01-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多