在Mathematica中,有没有一种高效简便的方法来比较两个长度相同的列表?

12

给定两个列表A={a1,a2,a3,...an}B={b1,b2,b3,...bn},当且仅当所有的ai>=bi时,我将说A>=B

虽然有内置的逻辑比较两个列表的操作A==B,但没有A>B这种形式。我们需要像下面这样逐个元素地进行比较吗?

And@@Table[A[[i]]>=B[[i]],{i,n}]

还有更好的方法吗?

编辑: 非常感谢大家。

以下是进一步的问题:

如何在N个相同长度的列表中找到最大的一个(如果存在)?

有没有使用Mathematica在N个相同长度的列表中找到最大的一个(如果存在)的有效简便方法?

5个回答

18

方法 1:我更喜欢这种方法。

NonNegative[Min[a - b]]

方法 2: 这只是为了好玩。正如Leonid所指出的那样,对于我使用的数据,它给了一点不公平的优势。 如果进行成对比较,并在适当时返回 False 和 Break, 那么循环可能更有效(尽管我通常避免在mma中使用循环):

result = True;
n = 1; While[n < 1001, If[a[[n]] < b[[n]], result = False; Break[]]; n++]; result

一些关于一百万个数字列表的时间比较:

a = Table[RandomInteger[100], {10^6}];
b = Table[RandomInteger[100], {10^6}];

(* OP's method *)
And @@ Table[a[[i]] >= b[[i]], {i, 10^6}] // Timing

(* acl's uncompiled method *)
And @@ Thread[a >= b] // Timing

(* Leonid's method *)
lessEqual[a, b] // Timing

(* David's method #1 *)
NonNegative[Min[a - b]] // Timing

timings 2


编辑:我已经移除了Method #2的时间数据,因为它们可能会误导人。Method #1更适合作为一般方法。


2
当然,这并不会减少我对你美丽而快速的第一种方法的钦佩。 - Leonid Shifrin
@Leonid。我也更喜欢方法#1。方法#2是为了检查Th尽早退出循环的速度优势。您对列表生成的看法非常正确:如果数字随机分配给a,b,则方法#2会获得不公平的优势。 - DavidC
@acl 我们确实需要协作评论(就像在PiratePad中一样),不是吗 :) ? - Leonid Shifrin
@acli 我不知道如何运行您编译的代码,但如果您告诉我应该怎么做,我很乐意将其包含在比较中。我是否需要在我的机器上安装C? - DavidC
@David 我没有想到在某些系统上可能没有C编译器。如果你没有,你可以将“C”替换为“WVM”。不过这会慢30倍。当我运行所有示例时,编译成C的那个明显是最快的,正如预期的那样(我说的是最坏情况)。 - acl
显示剩余5条评论

8
例如,
And @@ Thread[A >= B]

should do the job.

EDIT: On the other hand, this

cmp = Compile[
  {
   {a, _Integer, 1},
   {b, _Integer, 1}
   },
  Module[
   {flag = True},
   Do[
    If[Not[a[[p]] >= b[[p]]], flag = False; Break[]],
    {p, 1, Length@a}];
   flag],
  CompilationTarget \[Rule] "C"
  ]

速度快20倍。但丑陋了20倍。

编辑2:由于David没有C编译器可用,这里列出了所有的时间结果,有两个差异。首先,他的第二种方法已经被修复以比较所有元素。其次,我将a与自身进行比较,这是最坏的情况(否则,上面我的第二种方法只需要比较到第一个违反条件的元素)。

(*OP's method*)
And @@ Table[a[[i]] >= b[[i]], {i, 10^6}] // Timing

(*acl's uncompiled method*)
And @@ Thread[a >= b] // Timing

(*Leonid's method*)
lessEqual[a, b] // Timing

(*David's method #1*)
NonNegative[Min[a - b]] // Timing

(*David's method #2*)
Timing[result = True;
 n = 1; While[n < Length[a], 
  If[a[[n]] < b[[n]], result = False; Break[]];
  n++]; result]

(*acl's compiled method*)
cmp[a, a] // Timing

输入图像描述

因此,编译后的方法要快得多(请注意,David的第二种方法和此处的编译方法是相同的算法,唯一的区别是开销)。

所有这些都是在电池供电的情况下进行的,因此可能会有一些随机波动,但我认为它们是代表性的。

编辑3:如果像ruebenko在评论中建议的那样,将Part替换为Compile`GetElement,就像这样:

cmp2 = Compile[{{a, _Integer, 1}, {b, _Integer, 1}}, 
  Module[{flag = True}, 
   Do[If[Not[Compile`GetElement[a, p] >= Compile`GetElement[b, p]], 
     flag = False; Break[]], {p, 1, Length@a}];
   flag], CompilationTarget -> "C"]

那么cmp2cmp快两倍。

1
+1. 你可能可以将其简化一些:cmp1 = Compile[{{a, _Integer, 1}, {b, _Integer, 1}}, Do[If[a[[p]] < b[[p]], Return[False]], {p, 1, Length@a}] != False, CompilationTarget -> "C"] - Leonid Shifrin
编译明显更快。尽管如此,我还是很惊讶我的第一种方法表现得这么好。 - DavidC
@David 或许是因为 DeveloperPackedArrayQ[a - b]返回了True`,所以速度这么快。 - acl
1
尝试一下Compile`GetElement[a,p],而不是Part[a,p]和第二个。 - user1054186
我的第二种方法绝对不适用于大型列表,当结果为True时。 - DavidC
除非编译,否则 @David 是最快的(与上面的 cmp 相同)。特别是通过 ruebenko 的未记录技巧! - acl

4

既然你在问题中提到了效率,那么以下这些函数可能会对你有帮助:

ClearAll[lessEqual, greaterEqual];
lessEqual[lst1_, lst2_] :=
   SparseArray[1 - UnitStep[lst2 - lst1]]["NonzeroPositions"] === {};

greaterEqual[lst1_, lst2_] :=
   SparseArray[1 - UnitStep[lst1 - lst2]]["NonzeroPositions"] === {};

这些函数将会相当高效。@David的解决方案仍然比这个快两到四倍,如果你想要极速,而且列表是数字类型(由整数或实数构成),那么你应该考虑将其编译成C语言(使用@acl的解决方案或其他运算符类似)。
你可以使用相同的技术(使用Unitize代替UnitStep来实现等于和不等于),来实现其他比较运算符(>, <, ==, !=)。请记住,UnitStep[0]==1。

根据我的计时结果,你的函数比我的慢了四倍以上。 - DavidC
@David 我认为这取决于数据。在我的测试中,差异是2倍。但是,它们确实比您的第一个函数慢。尽管如此,我仍然会保留这个答案,因为它展示了SparseArray的使用方式,但我同意您的解决方案更优秀。 - Leonid Shifrin
我提到了基于方法1的时序比率(因为方法2只是为了好玩)。 - DavidC
@David 我也是指方法#1。我有:largeTst1 = RandomInteger[1000, 10000000];largeTst2 = RandomInteger[{999, 1990}, 10000000];In[95]:= NonNegative[Min[largeTst2-largeTst1]]//Timing Out[95]= {0.125,False}; In[96]:= greaterEqual[largeTst2,largeTst1]//Timing Out[96]= {0.234,False} - Leonid Shifrin

3
比较函数(如“Greater,GreaterEqual,Equal,Less,LessEqual”)可以通过多种方式应用于列表(它们都是您问题中方法的变体)。使用两个列表:
 a={a1,a2,a3};
 b={b1,b2,b3};

并且有两个数字条目的实例

na={2,3,4}; nb={1,3,2}; 

你可以使用

And@@NonNegative[na-nb]

用具有符号条目的列表
And@@NonNegative[na-nb]

提供

NonNegative[a1 - b1] && NonNegative[a2 - b2] && NonNegative[a3 - b3]

对于一般的比较,可以创建一个通用的比较函数,如下所示:

listCompare[comp_ (_Greater | _GreaterEqual | _Equal | _Less | _LessEqual), 
         list1_List, list2_List] := And @@ MapThread[comp, {list1, list2}]

使用 as 关键字

listCompare[GreaterEqual,na,nb]

使用符号输入,True返回。

listCompare[GreaterEqual,a,b]

给出逻辑上等价的表达式 a1 <= b1 && a2 <= b2 && a3 <= b3


2

当处理打包数组和数值比较器(如>=)时,使用David的方法#1难以超越。

但是,对于无法转换为简单算术的更复杂的测试,需要另一种方法。

一个很好的通用方法,特别适用于未打包的列表,是使用Inner

Inner[test, a, b, And]

这种方法不事先进行所有比较,因此在某些情况下比例如And @@ MapThread[test, {a, b}]更加高效。以下是两者之间的区别:

test = (Print[#, " >= ", #2]; # >= #2) &;

{a, b} = {{1, 2, 3, 4, 5}, {1, 3, 3, 4, 5}};

Inner[test, a, b, And]
1 >= 1
2 >= 3

False
And @@ MapThread[test, {a, b}]
1 >= 1
2 >= 3
3 >= 3
4 >= 4
5 >= 5

False
如果数组被压缩,尤其是当返回值为False的可能性较高时,像David's Method #2这样的循环是一个不错的选择。最好将代码编写为:
Null === Do[If[a[[i]] ~test~ b[[i]], , Return@False], {i, Length@a}]
1 >= 1
2 >= 3

False

其实这并不难,我的答案中有一个方法大致上快了一个数量级...(Inner的好提示,我之前不知道它的效率优势,+1) - acl
@acl 我不认为编译成C算数。;-)(我知道我可能应该克服这个问题,但是在v7中没有它和需要以那种可怕的过程式风格编写,我还没有准备好将其作为Mathematica的一部分接受。)感谢您的支持。 - Mr.Wizard
为什么你说“如果数组是紧凑的...”然后循环是一个好选择?如果我解包a并设置b=a,那么解包列表版本比打包的版本稍微慢一点。也就是说,打包并不会真正影响Do等函数的性能(尽管它们似乎没有解包)。或者我误解了吗? - acl
@acli 我的意思是,即使第一对不通过“test”,使用“Inner”仍会有显着的解包开销。在“Do”中使用“Part”可以避免这种情况。如果你理解我的意思,并且能够想到更好的表述方式,请告诉我。 - Mr.Wizard

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接