从Mathematica中的列表末尾搜索

时间:2011-09-16 14:13:30

标签: wolfram-mathematica

许多算法(如用于按字典顺序查找列表的下一个排列的算法)涉及查找列表中最后一个元素的索引。但是,我还没有找到一种方法在Mathematica中做到这一点并不尴尬。最简单的方法使用LengthWhile,但这意味着反转整个列表,如果您知道所需的元素接近列表的末尾并颠倒谓词的意义,则可能效率低下:< / p>

findLastLengthWhile[list_, predicate_] :=
 (Length@list - LengthWhile[Reverse@list, ! predicate@# &]) /. (0 -> $Failed)

我们可以使用Do做一个明确的,命令式的循环,但这也很有点笨拙。如果Return实际上从函数而不是Do块返回会有所帮助,但它没有,所以你不妨使用Break

findLastDo[list_, pred_] :=
 Module[{k, result = $Failed},
  Do[
   If[pred@list[[k]], result = k; Break[]],
   {k, Length@list, 1, -1}];
  result]

最终,我决定使用尾递归进行迭代,这意味着提前终止更容易一些。使用允许匿名函数调用自身的奇怪但有用的#0符号,这变为:

findLastRecursive[list_, pred_] :=
 With[{
   step =
    Which[
      #1 == 0, $Failed,
      pred@list[[#1]], #1,
      True, #0[#1 - 1]] &},
  step[Length@list]]
但是,所有这一切似乎都太难了。有没有人看到更好的方法?

编辑添加:当然,我首选的解决方案有一个错误,这意味着由于$IterationLimit,它在长列表中被破坏了。

In[107]:= findLastRecursive[Range[10000], # > 10000 &]
$IterationLimit::itlim: Iteration limit of 4096 exceeded. 
Out[107]= (* gack omitted *)

您可以使用Block

解决此问题
findLastRecursive[list_, pred_] :=
 Block[{$IterationLimit = Infinity},
  With[{
    step =
     Which[
       #1 == 0, $Failed,
       pred@list[[#1]], #1,
       True, #0[#1 - 1]] &},
   step[Length@list]]]

$IterationLimit不是我最喜欢的Mathematica功能。

6 个答案:

答案 0 :(得分:8)

不是真正的答案,只是findLastDo上的几个变种。

(1)实际上,Return可以采用无证的第二个参数来告诉从哪里返回。

In[74]:= findLastDo2[list_, pred_] := 
 Module[{k, result = $Failed}, 
  Do[If[pred@list[[k]], Return[k, Module]], {k, Length@list, 1, -1}];
  result]

In[75]:= findLastDo2[Range[25], # <= 22 &]
Out[75]= 22

(2)更好的是使用Catch [... Throw ...]

In[76]:= findLastDo3[list_, pred_] := 
 Catch[Module[{k, result = $Failed}, 
   Do[If[pred@list[[k]], Throw[k]], {k, Length@list, 1, -1}];
   result]]

In[77]:= findLastDo3[Range[25], # <= 22 &]
Out[77]= 22

Daniel Lichtblau

答案 1 :(得分:7)

对于喜欢冒险的人来说......

以下定义定义了一个包装表达式reversed[...],它伪装成一个列表对象,其内容看起来是包装列表的反转版本:

reversed[list_][[i_]] ^:= list[[-i]]
Take[reversed[list_], i_] ^:= Take[list, -i]
Length[reversed[list_]] ^:= Length[list]
Head[reversed[list_]] ^:= List

样品使用:

$list = Range[1000000];
Timing[LengthWhile[reversed[$list], # > 499500 &]]
(* {1.248, 500500} *)

请注意,此方法较慢比实际反转列表...

Timing[LengthWhile[Reverse[$list], # > 499500 &]]
(* 0.468, 500500 *)

...但当然它使用的内存要少得多。

我不建议将此技术用于一般用途,因为假面舞会中的缺陷可能表现为微妙的错误。考虑一下:其他函数需要实现什么来使模拟完美?展示的包装器定义显然足以愚弄LengthWhileTakeWhile的简单情况,但其他函数(特别是内置内置函数)可能不会那么容易被愚弄。压倒一切Head似乎特别充满了危险。

尽管存在这些缺点,但这种假冒技术有时在受控环境中有用。

答案 2 :(得分:6)

就个人而言,我认为基于LengthWhile的解决方案没有任何问题。此外,如果我们想要重用mma内置的列表遍历函数(而不是显式循环或递归),我看不到避免恢复列表的方法。这是一个版本,但不会反转谓词:

Clear[findLastLengthWhile];
findLastLengthWhile[{}, _] = 0;
findLastLengthWhile[list_, predicate_] /; predicate[Last[list]] := Length[list];
findLastLengthWhile[list_, predicate_] :=
   Module[{l = Length[list]}, 
     Scan[If[predicate[#], Return[], l--] &, Reverse[list]]; l];

是否更简单我不知道。它肯定比基于LengthWhile的效率低,特别是对于打包数组。此外,当没有找到满足条件的元素时,我使用返回0的约定,而不是$Failed,但这只是个人偏好。

修改

这是一个基于链表的递归版本,效率更高一些:

ClearAll[linkedList, toLinkedList];
SetAttributes[linkedList, HoldAllComplete];
toLinkedList[data_List] := Fold[linkedList, linkedList[], data];

Clear[findLastRec];
findLastRec[list_, pred_] :=
  Block[{$IterationLimit = Infinity},
     Module[{ll = toLinkedList[list], findLR},
       findLR[linkedList[]] := 0;
       findLR[linkedList[_, el_?pred], n_] := n;
       findLR[linkedList[ll_, _], n_] := findLR[ll, n - 1];
       findLR[ll, Length[list]]]]

一些基准:

In[48]:= findLastRecursive[Range[300000],#<9000&]//Timing
Out[48]= {0.734,8999}

In[49]:= findLastRec[Range[300000],#<9000&]//Timing
Out[49]= {0.547,8999}

编辑2

如果您的列表可以是一个打包的数组(无论什么尺寸),那么您可以利用C语言编译基于循环的解决方案。为了避免编译开销,您可以记住编译的函数,如下所示:

Clear[findLastLW];
findLastLW[predicate_, signature_] := findLastLW[predicate, Verbatim[signature]] = 
   Block[{list},
       With[{sig = List@Prepend[signature, list]},
      Compile @@ Hold[
        sig,
        Module[{k, result = 0},
          Do[
            If[predicate@list[[k]], result = k; Break[]], 
            {k, Length@list, 1, -1}
          ];
          result], 
        CompilationTarget -> "C"]]]

Verbatim部分是必要的,因为在{_Integer,1}这样的典型签名中,_Integer否则将被解释为模式,并且备忘的定义将不匹配。这是一个例子:

In[60]:= 
fn = findLastLW[#<9000&,{_Integer,1}];
fn[Range[300000]]//Timing

Out[61]= {0.016,8999}

编辑3

这是一个基于链表的更紧凑,更快速的递归解决方案版本:

Clear[findLastRecAlt];
findLastRecAlt[{}, _] = 0;
findLastRecAlt[list_, pred_] :=
  Module[{lls, tag},
    Block[{$IterationLimit = Infinity, linkedList},
       SetAttributes[linkedList, HoldAllComplete];
       lls = Fold[linkedList, linkedList[], list];
       ll : linkedList[_, el_?pred] := Throw[Depth[Unevaluated[ll]] - 2, tag];
       linkedList[ll_, _] := ll;
       Catch[lls, tag]/. linkedList[] :> 0]]

它与基于Do - 循环的版本一样快,比原始findLastRecursive快两倍(即将添加相关基准 - 我无法与之前的基准一致)此刻在另一台机器上)。我认为这很好地说明了mma中的尾递归解决方案与程序(未编译)解决方案一样有效。

答案 3 :(得分:3)

以下是一些备选方案,其中两个不反转列表:

findLastLengthWhile2[list_, predicate_] := 
 Length[list]-(Position[list//Reverse, _?(!predicate[#] &),1,1]/.{}->enter image description here)[[1, 1]]+1

findLastLengthWhile3[list_, predicate_] := 
    Module[{lw = 0}, 
      Scan[If[predicate[#], lw++, lw = 0] &, list]; 
      Length[list] - lw
    ]

findLastLengthWhile4[list_, predicate_] := 
   Module[{a}, a = Split[list, predicate]; 
         Length[list] - If[predicate[a[[-1, 1]]], Length[a[[-1]]], 0]
   ]

一些时间(数字1是Pillsy的第一个)在100,000个1的数组中找到1的最后一次运行,其中一个零被置于不同的位置。时间是10次重复使用的平均值:

{{0}}

用于计时的代码:

Monitor[
 timings = Table[
   ri = ConstantArray[1, {100000}];
   ri[[daZero]] = 0;
   t1 = (a1 = findLastLengthWhile[ri, # == 1 &];) // Timing // First;
   t2 = (a2 = findLastLengthWhile2[ri, # == 1 &];) // Timing // First;
   t3 = (a3 = findLastLengthWhile3[ri, # == 1 &];) // Timing // First;
   t4 = (a4 = findLastLengthWhile4[ri, # == 1 &];) // Timing // First;
   {t1, t2, t3, t4},
   {daZero, {1000, 10000, 20000, 50000, 80000, 90000, 99000}}, {10}
   ], {daZero}
 ]

ListLinePlot[
   Transpose[{{1000, 10000, 20000, 50000, 80000, 90000,99000}, #}] & /@ 
     (Mean /@ timings // Transpose), 
   Mesh -> All, Frame -> True, FrameLabel -> {"Zero position", "Time (s)", "", ""}, 
   BaseStyle -> {FontFamily -> "Arial", FontWeight -> Bold, 
   FontSize -> 14}, ImageSize -> 500
]

答案 4 :(得分:2)

字符串和实时的Reverse时间

a = DictionaryLookup[__];
b = RandomReal[1, 10^6];
Timing[Short@Reverse@#] & /@ {a, b}

(*
 ->
{{0.016,         {Zyuganov,Zyrtec,zymurgy,zygotic,zygotes,...}},
 {3.40006*10^-15,{0.693684,0.327367,<<999997>>,0.414146}}}
*)

答案 5 :(得分:0)

优雅的解决方案是:

findLastPatternMatching[{Longest[start___], f_, ___}, f_] := Length[{start}]+1

(* match this pattern if item not in list *)
findLastPatternMatching[_, _] := -1

但由于它基于模式匹配,因此它比其他解决方案建议的速度慢。