【问题标题】:How to iterate through a vector of vectors?如何遍历向量的向量?
【发布时间】:2017-04-15 18:58:45
【问题描述】:

我有这样的数据结构:

(defparameter *test*
  #(#(3)
    #(7 4)
    #(2 4 6)
    #(8 5 9 3)))

表示一个整数三角形和一个函数

(defun get-node (i j triangle)
  "Returns the value of the node I J of TRIANGLE."
  (assert (<= j i) (i j) "~&J has to be smaller than I! I: ~D J: ~D~%" i j)
  (svref (svref triangle i) j))

这让我可以轻松访问三角形的每个节点(至少我认为可以)。

我认为使用这两个可以很容易地沿着从顶部 (3) 到底部的三角形的不同路径建立总和,例如8 --> 3 + 7 + 2 + 8 = 20。下一条路径是 3 + 7 + 2 + 5 = 17。

不幸的是,在这个简单的示例中,我完全无法以合理的方式生成我的向量向量的索引以找到所有八条路径。这与暴力与动态编程等无关。我只是在寻找一种方法来遍历*test* 的列和行,以确保找到每条路径。

如何迭代这两个索引,例如ij 以这样的方式找到正确的组合?

【问题讨论】:

  • 什么路径? 3 + 7 + 2 + 5 = 15?那是什么路? 15?不是 17 岁?
  • 对不起,愚蠢的错字!
  • (1) 我们必须使用向量吗?尤其是高层? (2) 你失败的努力是什么样的? (3) 是的,您对任务的描述需要工作。我现在意识到这就像一个球从一个倒置的钉子金字塔上掉下来,要么向左要么向右。
  • (1) 不,我只是认为这将是最紧凑的解决方案。据我所知,二叉树会导致一些冗余,因为节点可以通过不同的路径到达。 (2) 我手头没有我的尝试,但我会在我可以访问我的计算机后立即添加它们。 (3) 没错!

标签: loops vector common-lisp


【解决方案1】:

我想我现在更好地理解了你想要列举的内容,所以这里是 您对替代定义的问题的另一个答案 一条“路径”:一条路径可以描述为一系列方向, 向下 (0) 或向下 (1)。那是微不足道的映射 为无符号整数 0 ≤ path path-directions 其中每一位代表连续的方向。每条路径都是 方便地由这对表示(path-directionsnumber) 其中path-directions 是方向数,number 对位 0 到 path-directions-1 中的连续方向进行编码。

(defun gen-paths (path-directions)
  (loop for i below (expt 2 path-directions)
        collect i))

(gen-paths 3) => (0 1 2 3 4 5 6 7)

(defun path-to-directions (path-directions path)
  (loop for i downfrom (- path-directions 1) to 0
        collect (elt #(:down :down-right)
                     (ldb (byte 1 i) path))))

(loop for path in (gen-paths 3) collect (path-to-directions 3 path)) =>

((:DOWN :DOWN :DOWN)             (:DOWN :DOWN :DOWN-RIGHT)
 (:DOWN :DOWN-RIGHT :DOWN)       (:DOWN :DOWN-RIGHT :DOWN-RIGHT)
 (:DOWN-RIGHT :DOWN :DOWN)       (:DOWN-RIGHT :DOWN :DOWN-RIGHT)
 (:DOWN-RIGHT :DOWN-RIGHT :DOWN) (:DOWN-RIGHT :DOWN-RIGHT :DOWN-RIGHT))

请注意,path-directions 比 三角形。当您将路径表示为节点列表时 有一个额外的元素,起始节点 (0, 0)。

(defun path-to-ref (path-directions path)
  "Map a path to the list of (I J) pairs as understood by `get-node`."
  (loop for i upto path-directions
        for j = 0 then (+ j (ldb (byte 1 (- path-directions i)) path))
        collect (list i j)))

 

(loop with path-directions = (- (length *test*) 1)
      for path in (gen-paths path-directions)
      collect (path-to-ref path-directions path))

=>

(((0 0) (1 0) (2 0) (3 0)) ((0 0) (1 0) (2 0) (3 1))
 ((0 0) (1 0) (2 1) (3 1)) ((0 0) (1 0) (2 1) (3 2))
 ((0 0) (1 1) (2 1) (3 1)) ((0 0) (1 1) (2 1) (3 2))
 ((0 0) (1 1) (2 2) (3 2)) ((0 0) (1 1) (2 2) (3 3))) 

 

(defun get-path-nodes (path triangle)
  "Returns the values of the nodes along PATH in TRIANGLE"
  (loop with path-directions = (- (length triangle) 1)
        for (i j) in (path-to-ref path-directions path)
        collect (get-node i j triangle)))

然后您可以轻松获取值:

(loop with path-directions = (- (length *test*) 1)
      for path in (gen-paths path-directions)
      collect (get-path-nodes path *test*))

=>

((3 7 2 8) (3 7 2 5) (3 7 4 5) (3 7 4 9)
 (3 4 4 5) (3 4 4 9) (3 4 6 9) (3 4 6 3))

或将它们相加

(loop with path-directions = (- (length *test*) 1)
      for path in (gen-paths path-directions)
      collect (loop for v in (get-path-nodes path *test*)
                    sum v))

=>

(20 17 19 23 16 20 22 16)

【讨论】:

  • 太好了!我花了两个多小时无法将我的大脑包裹在这个摘要上。我从你的回答中学到了很多。
【解决方案2】:

不出所料,Lisp 对列表的支持更友好,所以我选择了:

 (defparameter *pyre*
  '((3)
    (7 4)
    (2 4 6)
    (8 5 9 3)))

我的第一个刺只是收集一棵树,重复但可能更容易理解:

 (defun summit (n levels)
   (destructuring-bind (this . more) levels
    (let ((me (nth n this)))
      (if more
          (list
           (list* me (summit n more))
           (list* me (summit (1+ n) more)))
        (list me)))))

通过评估测试:

 (summit 0 *pyre*)

现在收紧并计算总和:

(defun summit (n levels)
  (destructuring-bind (this . more) levels
    (let ((me (nth n this)))
      (if more
          (loop for fork below 2
                nconc (summit (+ n fork) more) into fork-sums
                finally (return (loop for fs in fork-sums
                                      collecting (+ me fs))))
        (list me)))))

如果您担心重新计算路径,我们可以在一个级别上用现有汽车的总和和在下一个级别计算的每个值来计算其中的缺点。留作练习。

附录:好的,不需要rplaca 来避免重复计算,这让我非常失望(但道理是,递归通过将数据沿隐含在数据中的路径汇集数据,从而使显式路径管理变得不必要):

(defun summit-ex (levels)
  (destructuring-bind (level . lower-levels) levels
    (if lower-levels 
      (loop with next-result = (let ((nr (summit-ex lower-levels)))
                                 (print nr)
                                 nr)
          for pos upfrom 0
          for value in level
          for next-values = (loop for fork below 2
                                  append (nth (+ pos fork) next-result))
          collect (loop for next-value in next-values
                        collecting (+ value next-value)))
      (mapcar 'list level))))

调试打印留在原地,因为我喜欢他们清楚地说明发生了什么:

((8) (5) (9) (3)) 
((10 7) (9 13) (15 9)) 
((17 14 16 20) (13 17 19 13)) 
((20 17 19 23 16 20 22 16))

同时,是的,有些语言对向量更友好,例如 Clojure:

(def pyre [[3]
           [7 4]
           [2 4 6]
           [8 5 9 3]])

(defn summit-ex [[level & lower-levels]]
  (if lower-levels
    (let [next-result (summit-ex lower-levels)]
      (for [pos (range (count level))]
        (let [value (nth level pos)
              next-values (mapcat #(nth next-result (+ pos %))
                                  [0 1])]
          (map #(+ value %) next-values))))
    (map list level)))

我,我更喜欢 Lisp。

【讨论】:

  • 伟大的灵感!我想使用列表以外的其他东西,因为我认为它会导致大量内存消耗等。也许这首先是错误的,因为使用现代硬件,额外的内存量是可以忽略的。即使将数据视为一棵树,最终也会产生更易于理解的代码。感谢您的宝贵时间!
  • Common Lisp 编译器生成相当不错/优化的代码,当然在列表方面也做得很好,所以我会等到发现问题后再进行优化。请注意,如果您来自许多其他语言,我会认为向量/数组是最熟悉/最舒适的数据结构,因此菜鸟从那里开始是可以理解的。给列表一个机会。
  • 我一定会的:-)
  • 嘿嘿,照我做的:把它分解成一个小例子,比如 ((3)(7 4))。然后查看递归的结束条件,当没有更多数据可用时。那将是处理(7 4)。我的经验让我看到了一些前进并将每个步骤包装在一个列表中,但是返回值并稍后发现更高级别将期望每个分叉生成一个值列表是可以的。我经历了很多次迭代才得出这个结论,所以不要难过。而且我不应该使用循环,因为这是一个强大但不清晰的 DSL,我在看到光明之前已经鄙视了多年。
  • 查看labels,而不是顶部的包装器。您可以将递归牛肉移动到内部标签函数 Summit-beef 中,然后:(loop for sum in (first (summit-beef input-levels)) 最大化总和)。
【解决方案3】:

要生成“路径”,即(i j)s 的序列,您可以这样做:

(defun gen-paths (depth)
  (if (plusp depth)
      (loop for p in (gen-paths (- depth 1))
            nconc (loop for j to depth
                        collect (append p (list (list depth j)))))
      '(((0 0)))))

例如(gen-paths 3) =>

(((0 0) (1 0) (2 0) (3 0)) ((0 0) (1 0) (2 0) (3 1)) ((0 0) (1 0) (2 0) (3 2)) ((0 0) (1 0) (2 0) (3 3))
 ((0 0) (1 0) (2 1) (3 0)) ((0 0) (1 0) (2 1) (3 1)) ((0 0) (1 0) (2 1) (3 2)) ((0 0) (1 0) (2 1) (3 3))
 ((0 0) (1 0) (2 2) (3 0)) ((0 0) (1 0) (2 2) (3 1)) ((0 0) (1 0) (2 2) (3 2)) ((0 0) (1 0) (2 2) (3 3))
 ((0 0) (1 1) (2 0) (3 0)) ((0 0) (1 1) (2 0) (3 1)) ((0 0) (1 1) (2 0) (3 2)) ((0 0) (1 1) (2 0) (3 3))
 ((0 0) (1 1) (2 1) (3 0)) ((0 0) (1 1) (2 1) (3 1)) ((0 0) (1 1) (2 1) (3 2)) ((0 0) (1 1) (2 1) (3 3))
 ((0 0) (1 1) (2 2) (3 0)) ((0 0) (1 1) (2 2) (3 1)) ((0 0) (1 1) (2 2) (3 2)) ((0 0) (1 1) (2 2) (3 3)))

然后您可以将get-node 映射到此,以将这些索引转换为相应的值:

(loop for path in (gen-paths 3)
      collect (loop for (i j) in path
                    collect (get-node i j *test*)))

=>

((3 7 2 8) (3 7 2 5) (3 7 2 9) (3 7 2 3)
 (3 7 4 8) (3 7 4 5) (3 7 4 9) (3 7 4 3)
 (3 7 6 8) (3 7 6 5) (3 7 6 9) (3 7 6 3)
 (3 4 2 8) (3 4 2 5) (3 4 2 9) (3 4 2 3)
 (3 4 4 8) (3 4 4 5) (3 4 4 9) (3 4 4 3)
 (3 4 6 8) (3 4 6 5) (3 4 6 9) (3 4 6 3))

或将每个“路径”的值相加

(loop for path in (gen-paths 3)
      collect (loop for (i j) in path
                    summing (get-node i j *test*)))

=>

(20 17 21 15 22 19 23 17 24 21 25 19 17 14 18 12 19 16 20 14 21 18 22 16)

【讨论】:

  • 看起来很有趣!但是您的解决方案提供了许多途径。我想我的问题对这个细节不够清楚。在我的数据结构中,您只能直接向下或向下并向右走一步。如果我有时间我会看看我是否可以自己添加这个细节。
猜你喜欢
  • 1970-01-01
  • 2015-10-07
  • 1970-01-01
  • 2021-08-26
  • 1970-01-01
  • 2012-09-12
  • 2015-08-17
  • 2014-06-11
相关资源
最近更新 更多