许多算法(如用于按字典顺序查找列表的下一个排列的算法)涉及查找列表中最后一个元素的索引。但是,我还没有找到一种方法在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功能。
答案 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 *)
...但当然它使用的内存要少得多。
我不建议将此技术用于一般用途,因为假面舞会中的缺陷可能表现为微妙的错误。考虑一下:其他函数需要实现什么来使模拟完美?展示的包装器定义显然足以愚弄LengthWhile
和TakeWhile
的简单情况,但其他函数(特别是内置内置函数)可能不会那么容易被愚弄。压倒一切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]/.{}->)[[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
但由于它基于模式匹配,因此它比其他解决方案建议的速度慢。