从Mathematica中的列表末尾搜索

Pil*_*lsy 10 wolfram-mathematica

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

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

我们可以做一个明确的,强制性的循环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]
Run Code Online (Sandbox Code Playgroud)

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

findLastRecursive[list_, pred_] :=
 With[{
   step =
    Which[
      #1 == 0, $Failed,
      pred@list[[#1]], #1,
      True, #0[#1 - 1]] &},
  step[Length@list]]
Run Code Online (Sandbox Code Playgroud)

但这一切看起来都太难了.有没有人看到更好的方法?

编辑添加:当然,我的首选解决方案有一个错误,这意味着它在长列表中被打破,因为$IterationLimit.

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

你可以解决这个问题Block:

findLastRecursive[list_, pred_] :=
 Block[{$IterationLimit = Infinity},
  With[{
    step =
     Which[
       #1 == 0, $Failed,
       pred@list[[#1]], #1,
       True, #0[#1 - 1]] &},
   step[Length@list]]]
Run Code Online (Sandbox Code Playgroud)

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

Dan*_*lau 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
Run Code Online (Sandbox Code Playgroud)

(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
Run Code Online (Sandbox Code Playgroud)

Daniel Lichtblau


WRe*_*ach 7

喜欢冒险...

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

reversed[list_][[i_]] ^:= list[[-i]]
Take[reversed[list_], i_] ^:= Take[list, -i]
Length[reversed[list_]] ^:= Length[list]
Head[reversed[list_]] ^:= List
Run Code Online (Sandbox Code Playgroud)

样品用途:

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

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

Timing[LengthWhile[Reverse[$list], # > 499500 &]]
(* 0.468, 500500 *)
Run Code Online (Sandbox Code Playgroud)

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

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

尽管存在这些缺点,但这种模仿技术有时在受控环境中是有用的.

  • +1我不知道是否鼓掌或藏在我的桌子底下! (2认同)

Leo*_*rin 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];
Run Code Online (Sandbox Code Playgroud)

是否更简单我不知道.它肯定比基于它的效率低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]]]]
Run Code Online (Sandbox Code Playgroud)

一些基准:

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

In[49]:= findLastRec[Range[300000],#<9000&]//Timing
Out[49]= {0.547,8999}
Run Code Online (Sandbox Code Playgroud)

编辑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"]]]
Run Code Online (Sandbox Code Playgroud)

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

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

Out[61]= {0.016,8999}
Run Code Online (Sandbox Code Playgroud)

编辑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]]
Run Code Online (Sandbox Code Playgroud)

它与基于Do- 循环的版本一样快,并且比原始版本快两倍findLastRecursive(即将添加的相关基准测试 - 我目前无法在不同的机器上执行一致的(以前的)基准测试).我认为这很好地说明了mma中的尾递归解决方案与程序(未编译)解决方案一样有效.