Lisp 中的数组与列表:为什么下面的代码中的列表要快得多?

2024-04-03

我在解决时得到了意想不到的结果欧拉计划中的问题 75 https://projecteuler.net/problem=75。我的代码确实找到了正确的解决方案,但它的行为很奇怪。

我的解决方案包括遍历毕达哥拉斯树(巴宁矩阵 https://en.wikipedia.org/wiki/Tree_of_primitive_Pythagorean_triples)直到达到周长限制,计算周长呈现每个值的次数,最后计算仅出现一次的周长长度。我诚然不整洁但功能正常的代码是:

(defparameter *barning-matrixes*
   '(#(1 -2  2) #(2 -1  2) #(2 -2  3)
     #(1  2  2) #(2  1  2) #(2  2  3)
     #(-1 2  2) #(-2 1  2) #(-2 2  3)))

(defparameter *lengths* (make-array 1500001 :initial-element 0))

(defun expand-node (n)
"Takes a primitive Pythagorean triple in a vector and traverses subsequent nodes in the the tree of primitives until perimeter > 1,500,000"
   (let ((perimeter (reduce #'+ n)))
   (unless (> perimeter 1500000)
     (let ((next-nodes (mapcar #'(lambda (x)
                                   (reduce #'+ (map 'vector #'* n x))) *barning-matrixes*)))
        (loop for i from perimeter to 1500000 by perimeter
              do (incf (aref *lengths* i)))
        (expand-node (subseq next-nodes 0 3))
        (expand-node (subseq next-nodes 3 6))
        (expand-node (subseq next-nodes 6 9))))))

(expand-node #(3 4 5))  ; Takes too darn long :-(
(count 1 *lengths*)

我预计树扩展会在几毫秒内运行,但扩展节点函数花了 8.65 秒(比预期长得多)来遍历一棵不是很大的树。

然而,当我调整代码以删除向量时,我感到很惊讶......

(defparameter *barning-matrixes*
   '((1 -2  2) (2 -1  2) (2 -2  3)
     (1  2  2) (2  1  2) (2  2  3)
     (-1 2  2) (-2 1  2) (-2 2  3)))


(defparameter *lengths* (make-array 1500001 :initial-element 0))

(defun expand-node (n)
"Takes a primitive Pythagorean triple in a list and traverses subsequent nodes in the the tree of primitives until perimeter > 1,500,000"
   (let ((perimeter (reduce #'+ n)))
   (unless (> perimeter 1500000)
     (let ((next-nodes (mapcar #'(lambda (x) (reduce #'+ (mapcar #'* n x))) *barning-matrixes*)))
        (loop for i from perimeter to 1500000 by perimeter
              do (incf (aref *lengths* i)))
        (expand-node (subseq next-nodes 0 3))
        (expand-node (subseq next-nodes 3 6))
        (expand-node (subseq next-nodes 6 9))))))

(expand-node '(3 4 5))  ; Much faster, but why?!
(count 1 *lengths*)

...并且遍历速度快得多,只花了 35 毫秒。我对这种巨大的差异很感兴趣,希望有人能解释为什么会发生这种情况。

谢谢, 保罗

PS:我使用 CCL 来完成这一切。


您没有说您正在使用哪种实现。

您需要找出时间花在哪里。

但对我来说,它看起来像实施MAP在 Common Lisp 中将列表和长度相等的向量转换为新向量可能效率非常低。 即使使用新的向量(有一些开销),实现也会快得多。

尝试将向量运算实现为循环并进行比较:

(loop with v = (make-array (length n))
      for n1 across n
      for x1 across x
      for i from 0
      do (setf (aref v i) (* n1 x1))
      finally (return v))

这个更快的版本也有缺点,但用向量运算取代了列表运算:

(defparameter *barning-matrixes*
  #(#(1 -2  2) #(2 -1  2) #(2 -2  3) #(1  2  2) #(2  1  2) #(2  2  3) #(-1 2  2) #(-2 1  2) #(-2 2  3)))

(defparameter *lengths* (make-array 1500001 :initial-element 0))

(defun expand-node (n)
  "Takes a primitive Pythagorean triple in a vector and traverses subsequent nodes in the the tree of primitives until perimeter > 1,500,000"
  (let ((perimeter (reduce #'+ n)))
    (unless (> perimeter 1500000)
      (let ((next-nodes
             (loop with v = (make-array (length *barning-matrixes*))
                   for e across *barning-matrixes*
                   for i from 0
                   do (setf (aref v i)
                            (reduce #'+
                                    (loop with v = (make-array (length n))
                                          for n1 across n
                                          for x1 across e
                                          for i from 0
                                          do (setf (aref v i) (* n1 x1))
                                          finally (return v))))
                   finally (return v))))
        (loop for i from perimeter to 1500000 by perimeter
              do (incf (aref *lengths* i)))
        (expand-node (subseq next-nodes 0 3))
        (expand-node (subseq next-nodes 3 6))
        (expand-node (subseq next-nodes 6 9))))))

(time (expand-node #(3 4 5)))

让我们看看你的代码:

(defun expand-node (n)

; here we don't know of which type N is. You call it from the toplevel
; with a vector, but recursive calls call it with a list

  "Takes a primitive Pythagorean triple in a vector and traverses
 subsequent nodes in the the tree of primitives until perimeter > 1,500,000"
  (let ((perimeter (reduce #'+ n)))
    (unless (> perimeter 1500000)
      (let ((next-nodes (mapcar #'(lambda (x)    ; this mapcar creates a list
                                    (reduce #'+
                                            (map 'vector
                                                 #'*
                                                 n  ; <- list or vector
                                                 x))) ; <- vector
                                *barning-matrixes*)))
        (loop for i from perimeter to 1500000 by perimeter
              do (incf (aref *lengths* i)))
        (expand-node (subseq next-nodes 0 3))   ; this subseq returns a list most of the times...
        (expand-node (subseq next-nodes 3 6))
        (expand-node (subseq next-nodes 6 9))))))

所以你打电话MAP大多数时候都有一个列表和一个向量。 结果向量的大小是多少? MAP 必须通过遍历列表或通过其他方式来查找。结果向量长度是参数序列长度中最短的一个。然后它必须迭代列表和向量。如果 MAP 现在使用通用序列操作,则对列表的元素访问始终是遍历列表。显然,人们可以编写一个优化版本,它的速度更快,但 Common Lisp 实现可能会选择仅提供 MAP 的通用实现...

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Lisp 中的数组与列表:为什么下面的代码中的列表要快得多? 的相关文章

  • 在我的 Linux 机器上安装 lisp

    我使用 Vim 作为我的编辑器 Practical common Lisp 建议安装 Lispbox 我不知道如何使用 emacs 不知道如何用那个 T T 运行 lisp 代码 之后我找到了一个名为 limp vim 的 vim lisp
  • 截断浮点数而不向上舍入

    我有一个浮点数 我想将其截断为 3 位 但我不想向上舍入 例如 转换1 0155555555555555 to 1 015 not 1 016 我将如何在 Ruby 中做到这一点 您还可以转换为 BigDecimal 并对其调用 trunc
  • 在二维空间中从 A 点前往 B 点?

    我正在开发一个项目 需要我计算从可变点 A 到可变点 B 的 0 360 度航向 以使 A 点的物体面向 B 点 现在 我不确定如何实现这一目标 我用谷歌搜索但没有找到任何好的解决方案 在任何情况下 如何计算二维空间中从 A 点到 B 点的
  • 求反射角的弧度

    我正在编写一个简单的 Flash 游戏 只是为了学习 Flash 并提高我的数学能力 但我对弧度感到非常困惑 因为这对我来说是新的 到目前为止 我所做的是使用鼠标 单击并释放 使用弧度向该方向射出一个球 现在我想要发生的是 当球撞到墙壁时
  • 计算标签云中标签字体大小的公式是什么?

    我有一个标签云 我需要知道如何更改最常用标签的字体大小 我需要设置最小字体大小和最大字体大小 您可以使用线性或对数评估与某个标签相对于最大标签关联的项目数量 将其乘以最小和最大字体大小之间的差值 然后将其添加到最小字体大小 例如 伪代码中的
  • Android:如何获取小数点后的两位数?不想截断值

    如何获取小数点后仅两位数的双精度值 例如 如果 a 190253 80846153846 那么结果值应该像 a 190253 80 尝试 我尝试过这个 public static DecimalFormat twoDForm new Dec
  • 优雅的折线“左移”测试

    Given X Y 坐标 即车辆的位置 X Y 数组 它们是折线中的顶点 请注意 折线仅由直线段组成 没有圆弧 我想要的是 计算车辆是在折线的左侧还是右侧 当然还是在顶部 我的做法 迭代所有线段 并计算到每个线段的距离 然后 对于最近的段
  • 给定与总和匹配的长度的唯一 3 位数字 (-1,0,1) 序列的数量

    假设您有一个长度为 n 即空格数 的垂直游戏板 你有一个三面骰子 有以下选项 前进一 停留和后退 如果您低于或高于棋盘游戏空间的数量 则该游戏无效 一旦到达棋盘末端 唯一有效的动作就是 停留 给定确切的骰子投掷次数 t 是否可以通过算法计算
  • 确定范围是否重叠

    给定两个具有整数开始时间和结束时间的事件 E1 s1 e1 E2 s2 e2 实现快速布尔检查以查看事件是否重叠 我有解决方案 但我很想看看其他人想出了什么 编辑 好的 这是我的解决方案 e1 gt s2 s1 gt s2 e2 lt s1
  • 用圆形雷达数学方法表示点

    我正在编写一个简单的应用程序 它可以向您显示您周围的朋友 但不是在法线地图中 而是在像 UI 这样的真正圆形雷达上 https i stack imgur com Au3IP png https i stack imgur com Au3I
  • Python 小数.InvalidOperation 错误

    当我运行这样的东西时 我总是收到此错误 from decimal import getcontext prec 30 b 2 3 Decimal b Error Traceback most recent call last File Te
  • 处理中渲染极地带面体时出现问题

    我最近一直在研究 Zohedrons 和Rob Bell http zomadic com 做出了美丽的 我玩了免费的极地带面体 Sketchup 插件 http zomebuilder com 并考虑使用几何图形加工 http proce
  • 矩阵向量变换

    我正在编写一个代码来制作软件蒙皮器 骨骼 皮肤动画 并且我正处于 优化 阶段 蒙皮器工作得很好 并且在 Core 上 1 09 毫秒内对 4900 个三角形网格与 22 个骨骼进行蒙皮Duo 2 Ghz 笔记本 我需要知道的是 1 有人可以
  • 按度数在圆上找到一个点?

    假设我们有一个 100x100 坐标系 如下所示 0 0 是它的左上角 50 50 是它的中心点 100 100 是它的右下角 等等 现在我们需要从中心向外画一条线 我们知道线的角度 但需要计算其终点的坐标 您认为最好的方法是什么 例如 如
  • 寻找将集合映射到整数的双射函数

    对于任意两个序列 a b 其中 a a1 a2 an 且 b b1 b2 bn 0a b具有相同的元素 而不关心它们的顺序 例如 如果 a 1 1 2 3 b 2 1 3 1 c 3 2 1 3 则 f a f b f a f b 我知道有
  • 如何自定义舍入形式

    我的问题可能看起来很简单 但仍然无法得到有效的东西 我需要自定义 Math round 舍入格式或其他格式以使其工作如下 如果数字是 1 6 他应该四舍五入到 1 如果大于或等于 1 7 他应该四舍五入到 2 0 对于所有其他带有 6 的小
  • 两个整数乘积的模

    我必须找到c c a b mod m a b c m 是 32 位整数 但 a b 可以超过 32 位 我正在尝试找出一种计算 c 的方法 而不使用 long 或任何 gt 32 位的数据类型 有任何想法吗 如果m是质数 事情可以简化吗 注
  • 在网络上编写数学方程的最佳方法是什么?

    Locked 这个问题及其答案是locked help locked posts因为这个问题是题外话 但却具有历史意义 目前不接受新的答案或互动 我正在开发一个与数学相关的网页 并正在寻找一种将数学方程轻松写入网页的解决方案 目前我可以使用
  • C++ 中求幂的函数是什么?

    如何计算一个数的幂 2 1 2 2 2 3 etc cmath 库中的 pow 更多信息here http en cppreference com w cpp numeric math pow 别忘了放 include
  • 比较批处理文件中的两个数字

    我在这个网站上搜索了我的问题 但没有找到解决我问题的方法 系统为玩家和计算机提供一个从 2 到 12 的随机数 这有 3 部分 X 大于 Y 如果 X 小于 Y 以及当 X 与 Y 相同 当我开始 bat 效果很好 我选择Play Game

随机推荐