左链表与右链表,替换速度

发布于 2024-11-03 06:14:46 字数 706 浏览 1 评论 0原文

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

{1, {2, {3, {4, {5, {6, {7, {}}}}}}}}

和“右”:

{{{{{{{{}, 7}, 6}, 5}, 4}, 3}, 2}, 1}

这些可以通过以下方式实现:

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

toRightLL = Fold[List, {}, Reverse@#] & ;

如果我使用这些,并执行一个简单的 ReplaceRepeated 来遍历链接列表中,我得到了截然不同的 Timing 结果:

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} *)

为什么?

There are two obvious ways to structure a linked list in Mathematica, "left":

{1, {2, {3, {4, {5, {6, {7, {}}}}}}}}

And "right":

{{{{{{{{}, 7}, 6}, 5}, 4}, 3}, 2}, 1}

These can be made with:

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

toRightLL = Fold[List, {}, Reverse@#] & ;

If I use these, and do a simple ReplaceRepeated to walk through the linked list, I get drastically different Timing results:

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} *)

Why?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

浪菊怪哟 2024-11-10 06:14:46

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

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

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}


EDIT In response to Mr.Wizard's question of options to speed this up. One should write a custom same testings. ReplaceRepeated does not provide such an option, so we should use FixedPoint and 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: Faster yet:

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

Out[162]= {0.124, 15000}

ReplaceRepeated uses SameQ to determine when to stop applying the rule.

When SameQ compares two lists, it checks lengths, and if the same, then applies SameQ to elements from the first to the last. In the case of left the first element is an integer, so it is easy to detect distinct lists, while for right list the first element is the deeply nested expression, so it needs to traverse it. This is the reason for the slowness.

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

Out[25]= {11.7091708, Null}

Now compare this with:

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

Out[31]= {5.351, 15000}


EDIT In response to Mr.Wizard's question of options to speed this up. One should write a custom same testings. ReplaceRepeated does not provide such an option, so we should use FixedPoint and 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: Faster yet:

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

Out[162]= {0.124, 15000}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文