Optimization 左与右链表,替换速度

Optimization 左与右链表,替换速度,optimization,linked-list,wolfram-mathematica,Optimization,Linked List,Wolfram Mathematica,在Mathematica中,有两种明显的方法可以构造链表,“左”: 和“对”: 这些可通过以下方式进行: toLeftLL = Fold[{#2, #} &, {}, Reverse@#] & ; toRightLL = Fold[List, {}, Reverse@#] & ; 如果我使用这些,并通过一个简单的ReplaceRepeated遍历链表,我会得到截然不同的计时结果: r = Range[15000]; left = toLeftLL@r; right =

在Mathematica中,有两种明显的方法可以构造链表,“左”:

和“对”:

这些可通过以下方式进行:

toLeftLL = Fold[{#2, #} &, {}, Reverse@#] & ;

toRightLL = Fold[List, {}, Reverse@#] & ;
如果我使用这些,并通过一个简单的
ReplaceRepeated
遍历链表,我会得到截然不同的
计时结果:

r = Range[15000];
left = toLeftLL@r;
right = toRightLL@r;

Timing[i = 0; left //. {head_, tail_} :> (i++; tail); i]
Timing[i = 0; right //. {tail_, head_} :> (i++; tail); i]

(* Out[6]= {0.016, 15000} *)

(* Out[7]= {5.437, 15000} *)

为什么?

ReplaceRepeated
使用
SameQ
确定何时停止应用规则

SameQ
比较两个列表时,它会检查长度,如果相同,则将
SameQ
应用于从第一个到最后一个元素。在
left
的情况下,第一个元素是一个整数,因此很容易检测不同的列表,而对于
right
list,第一个元素是深度嵌套的表达式,因此需要遍历它。这就是缓慢的原因

In[25]:= AbsoluteTiming[
 Do[Extract[right, ConstantArray[1, k]] === 
   Extract[right, ConstantArray[1, k + 1]], {k, 0, 15000 - 1}]]

Out[25]= {11.7091708, Null}
现在将其与:

In[31]:= Timing[i = 0; right //. {tail_, head_} :> (i++; tail); i]

Out[31]= {5.351, 15000}

编辑以回答Wizard先生提出的加快速度的选项问题。一个人应该编写一个自定义的相同测试
ReplaceRepeated
不提供这样的选项,因此我们应该使用
FixedPoint
ReplaceAll

In[61]:= Timing[i = 0; 
 FixedPoint[(# /. {tail_, _} :> (i++; tail)) &, right, 
  SameTest -> 
   Function[
    If[ListQ[#1] && ListQ[#2] && 
      Length[#1] == 
       Length[#2], (#1 === {} && #2 === {}) || (Last[#1] === 
        Last[#2]), #1 === #2]]]; i]

Out[61]= {0.343, 15000}

EDIT2:更快:

In[162]:= Timing[i = 0; 
 NestWhile[Function[# /. {tail_, head_} :> (i++; tail)], right, 
  Function[# =!= {}]]; i]

Out[162]= {0.124, 15000}

我想它可以更快,因为尾部调用优化。检查这个:@Mr.Wizard:你能分解你的
RuleDelayed
的RHS吗。虽然我想我看到了它是如何在列表中穿行的,但还不完全清楚。此外,如果我将RHS中的
tail
替换为
tail-tail+tail
,我会得到一个错误:
$RecursionLimit::reclim:递归深度超过256。>>并需要中止。为什么mma不能找出
tail-tail+tail=tail
并返回与以前相同的结果?@yoda,对于一个“左”列表,
{head,tail}:>(i++;tail)
递增
i
并返回链接列表的其余部分,而不返回第一个元素(head),例如
{2,{3,{4,{5,{6,{7}}}
如果在我问题的第一个列表中使用。我增加
I
只是为了证明每次更换都发生了15000次。模式
头部
仅用于清晰,也可以用
替换。由于
tail
是一个列表结构,并且算术运算在这些树中穿行,因此您最多可以执行14999个操作,而不是对每个
+
-
执行一个操作。啊
/.
!!我没有注意到这一点,而是试图用
/。
来理解如何完成漫游,这没有多大意义!现在我明白了!感谢您对评论第二部分的解释。您认为为什么这里涉及到
SameQ
?打开对
SameQ
的跟踪不会显示对它的任何调用:
打开[SameQ]
上的code>
将仅显示对
SameQ
符号的求值
ReplaceRepeated
在确定
ReplaceRepeated
应终止时,不调用计算器以提高效率。@Alexey否,所有用户提供的代码都通过计算器执行。这就是语言解释器的工作方式。@Mr.Wizard我已经给出了一种可能,可以在编辑答案时加快代码的速度。如果使用标准求值而不是“ReplaceRepeated”,则可以获得与“left”情况相同的性能:
loop@{t_,h_}:=(I++;loop@t);块[{$RecursionLimit=Infinity},计时[i=0;loop@right;i]]
In[61]:= Timing[i = 0; 
 FixedPoint[(# /. {tail_, _} :> (i++; tail)) &, right, 
  SameTest -> 
   Function[
    If[ListQ[#1] && ListQ[#2] && 
      Length[#1] == 
       Length[#2], (#1 === {} && #2 === {}) || (Last[#1] === 
        Last[#2]), #1 === #2]]]; i]

Out[61]= {0.343, 15000}
In[162]:= Timing[i = 0; 
 NestWhile[Function[# /. {tail_, head_} :> (i++; tail)], right, 
  Function[# =!= {}]]; i]

Out[162]= {0.124, 15000}