在 Mathematica 中从列表末尾搜索
许多算法(例如按字典顺序查找列表的下一个排列的算法)都涉及查找列表中最后一个元素的索引。然而,我一直无法在 Mathematica 中找到一种不尴尬的方法来做到这一点。最直接的方法使用 LengthWhile
,但这意味着反转整个列表,如果您知道所需的元素位于列表末尾附近,并且反转谓词的含义,这可能效率低下:
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 功能。
Many algorithms (like the algorithm for finding the next permutation of a list in lexicographical order) involve finding the index of the last element in a list. However, I haven't been able to find a way to do this in Mathematica that isn't awkward. The most straightforward approach uses LengthWhile
, but it means reversing the whole list, which is likely to be inefficient in cases where you know the element you want is near the end of the list and reversing the sense of the predicate:
findLastLengthWhile[list_, predicate_] :=
(Length@list - LengthWhile[Reverse@list, ! predicate@# &]) /. (0 -> $Failed)
We could do an explicit, imperative loop with Do
, but that winds up being a bit clunky, too. It would help if Return
would actually return from a function instead of the Do
block, but it doesn't, so you might as well use Break
:
findLastDo[list_, pred_] :=
Module[{k, result = $Failed},
Do[
If[pred@list[[k]], result = k; Break[]],
{k, Length@list, 1, -1}];
result]
Ultimately, I decided to iterate using tail-recursion, which means early termination is a little easier. Using the weird but useful #0
notation that lets anonymous functions call themselves, this becomes:
findLastRecursive[list_, pred_] :=
With[{
step =
Which[
#1 == 0, $Failed,
pred@list[[#1]], #1,
True, #0[#1 - 1]] &},
step[Length@list]]
All of this seems too hard, though. Does anyone see a better way?
EDIT to add: Of course, my preferred solution has a bug which means it's broken on long lists because of $IterationLimit
.
In[107]:= findLastRecursive[Range[10000], # > 10000 &]
$IterationLimit::itlim: Iteration limit of 4096 exceeded.
Out[107]= (* gack omitted *)
You can fix this with 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
is not my favorite Mathematica feature.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
并不是真正的答案,只是 findLastDo 的几个变体。
(1) 实际上 Return 可以采用未记录的第二个参数来告诉从什么返回。
(2) 更好的是使用 Catch[...Throw...]
Daniel Lichtblau
Not really an answer, just a couple of variants on findLastDo.
(1) Actually Return can take an undocumented second argument telling what to return from.
(2) Better is to use Catch[...Throw...]
Daniel Lichtblau
对于冒险...
以下定义定义了一个包装器表达式
reversed[...]
,它伪装成一个列表对象,其内容似乎是包装的反向版本list:示例使用:
请注意,此方法比实际反转列表慢
......但当然它使用的内存要少得多。
我不建议将此技术广泛使用,因为伪装中的缺陷可能会表现为微妙的错误。考虑一下:需要实现哪些其他功能才能使模拟完美?对于简单的情况,所展示的包装器定义显然足以欺骗
LengthWhile
和TakeWhile
,但其他函数(特别是内核内置函数)可能不会那么容易被欺骗。覆盖Head
似乎特别充满危险。尽管存在这些缺点,这种模拟技术有时在受控环境下还是有用的。
For the adventurous...
The following definitions define a wrapper expression
reversed[...]
that masquerades as a list object whose contents appear to be a reversed version of the wrapped list:Sample use:
Note that this method is slower than actually reversing the list...
... but of course it uses much less memory.
I would not recommend this technique for general use as flaws in the masquerade can manifest themselves as subtle bugs. Consider: what other functions need to implemented to make the simulation perfect? The exhibited wrapper definitions are apparently good enough to fool
LengthWhile
andTakeWhile
for simple cases, but other functions (particularly kernel built-ins) may not be so easily fooled. OverridingHead
seems particularly fraught with peril.Notwithstanding these drawbacks, this impersonation technique can sometimes be useful in controlled circumstances.
就我个人而言,我认为基于 LengthWhile 的解决方案没有任何问题。另外,如果我们想重用 mma 内置列表遍历函数(而不是显式循环或递归),我没有找到避免恢复列表的方法。这是一个版本,它可以做到这一点,但不会反转谓词:
我不知道它是否更简单。它肯定比基于 LengthWhile 的效率低,特别是对于打包数组。另外,当没有找到满足条件的元素时,我使用返回
0
的约定,而不是$Failed
,但这只是个人偏好。编辑
这是一个基于链表的递归版本,它在某种程度上更有效:
一些基准:
编辑2
如果您的列表可以制作为压缩数组(任何尺寸) ,那么您可以利用 C 编译来实现基于循环的解决方案。为了避免编译开销,您可以记住已编译的函数,如下所示:
Verbatim
部分是必要的,因为在{_Integer,1}
、_Integer 等典型签名中否则,
将被解释为模式,并且记忆的定义将不匹配。这是一个例子:EDIT 3
这是一个基于链表的递归解决方案的更紧凑和更快的版本:
它与基于
Do
的版本一样快 - 循环,比原来的 findLastRecursive 快两倍(很快就会添加相关基准 - 目前我无法在不同的机器上进行一致的(与以前的)基准测试)。我认为这很好地说明了 MMA 中的尾递归解决方案可以与过程(未编译)解决方案一样高效。Personally, I don't see anything wrong with
LengthWhile
-based solution. Also, if we want to reuse mma built-in list-traversing functions (as opposed to explicit loops or recursion), I don't see a way to avoid reverting the list. Here is a version that does that, but does not reverse the predicate:Whether or not it is simpler I don't know. It is certainly less efficient than the one based on
LengthWhile
, particularly for packed arrays. Also, I use the convention of returning0
when no element satisfying a condition is found, rather than$Failed
, but this is just a personal preference.EDIT
Here is a recursive version based on linked lists, which is somewhat more efficient:
Some benchmarks:
EDIT 2
If your list can be made a packed array (of whatever dimensions), then you can exploit compilation to C for loop-based solutions. To avoid the compilation overhead, you can memoize the compiled function, like so:
The
Verbatim
part is necessary since in typical signatures like{_Integer,1}
,_Integer
will otherwise be interpreted as a pattern and the memoized definition won't match. Here is an example:EDIT 3
Here is a much more compact and faster version of recursive solution based on linked lists:
It is as fast as versions based on
Do
- loops, and twice faster than the originalfindLastRecursive
(the relevant benchmark to be added soon - I can not do consistent (with previous) benchmarks being on a different machine at the moment). I think this is a good illustration of the fact that tail-recursive solutions in mma can be as efficient as procedural (uncompiled) ones.这里有一些替代方案,其中两个不会颠倒列表:
一些时间(数字 1 是 Pillsy 的第一个)在 100,000 个 1 的数组中找到最后一串 1,其中单个零被放置在各个位置上。计时是 10 次重复测量的平均值:
用于计时的代码:
Here are some alternatives, two of which don't reverse the list:
Some timings (number 1 is Pillsy's first one) of finding the last run of 1's in an array of 100,000 1's in which a single zero is placed on various positions. Timings are the mean of 10 repeated meusurements:
Code used for timings:
字符串和实数的时序
反向
Timing
Reverse
for Strings and Reals一个优雅的解决方案是:
但由于它基于模式匹配,因此它比建议的其他解决方案慢得多。
An elegant solution would be:
but as it's based on pattern matching, it's way slower than the other solutions suggested.