【问题标题】:Resources for Learning Box and Pointer Diagrams学习框和指针图资源
【发布时间】:2019-08-08 02:27:51
【问题描述】:

我目前正在通过Berkely's summer 2011 CS3L course 工作,并且正在努力理解方框图和指针图。如何构建它们以及如何解释它们。

提供的说明是here。 但是,我仍然没有“明白”。

我了解列表是对的组合,并且一对的 cdr 可能指向另一对。我也明白 cdr 指向的那对可能是另一个列表。我只是不明白如何在图表中全部绘制出来。

作为参考,这是我遇到的问题的一个示例:

(define cal 
  (list (append (list (cons (list 1 2) (cons 3 '())))
                (list (cons 4 (cons 5 '())))) 
        6 
        7))

给定一个类似上面的代码,我想画出盒子和指针图,然后能够说出 car 和 cdr 的组合需要得到列表中的任何给定数字。

再次,作为参考,下面是我应该能够想出的图表:

重申一下,我正在寻找的是可以更清楚地解释盒形图和指针图的构建的视频或文章。

提前感谢任何愿意为我指明正确方向的人。

【问题讨论】:

  • 感谢所有花时间回答的人。我通读了你所有的帖子,它们都在帮助我更好地理解这个概念。

标签: list linked-list scheme lisp nested-lists


【解决方案1】:

[请注意,此答案并不鼓励您作弊:如果您正在学习的课程要求您能够绘制箱形图和指针图,那么您应该能够做到这一点,而不是让程序为您完成.但该程序可以帮助您学习。]

学习盒子和指针图如何工作的一个好方法是能够与知道如何绘制它们的程序交谈。在 Lisp 很久以前的黄金时代,我们的 Lisp 机器上有很棒的对话界面,可以让图形和文本混合在一起,还有漂亮的绘图程序,可以很容易地从中构建工具来完成此操作。使用这些工具,您可以从 conses 中构建各种结构,并让程序为您绘制图表,从而很好地掌握这些结构的工作原理。

嗯......事实证明,Lisp 的黄金时代是现在。如果你使用Racket(如果你还没有使用Racket,你可以使用它),那么有一个非常棒的包sdraw可以做到这一点。它没有与 Racket 发行版捆绑在一起,但要安装它,您可以使用 DrRacket 的包管理器或直接安装

raco pkg install --auto sdraw

它将安装它。现在您可以(在 DrRacket 窗口中,这在终端会话中不起作用)只需与 Racket REPL 对话并让它为您绘制 cons 树:

通过简单地与语言进行交互并让它为您绘制东西,您就可以很好地了解各种结构如何结合在一起。

【讨论】:

  • 玩弄这个真的会帮助你很好地掌握这个。非常感谢!
【解决方案2】:

此答案使用 Common Lisp 作为示例,但在 Scheme 中并没有根本不同。另请注意,如果您想了解如何实际实现打印图表,可以在 Common Lisp 中使用sdraw.lisp(例如使用 CCL 或 SBCL 程序)。

您首先必须清楚要打印的结果,当源包含 cons/list/append 操作时,这可能会很困难。此外,由于源代码也是一个 cons-cells 树,因此您必须注意不要将源代码与评估后获得的值混在一起。 所以这一切都从正确评估表单开始。

评估cal

让我们首先评估表达式。下面,我还提到直接从输入表达式中绘制框,但 IMO 它有助于详细说明该中间步骤。 在对所有表达式进行递归计算后,Scheme 和 Common Lisp 中的结果是相同的:

((((1 2) 3) (4 5)) 6 7)

Common Lisp中,您可以通过以下方式要求系统跟踪计算。首先,要知道您无法跟踪 list 等标准函数。所以让我们用简单的包装器将它们隐藏在自定义包中:

(defpackage :so
  (:use :cl)
  (:shadow #:list #:cons #:append))

(in-package :so)

(defun list (&rest args) (apply #'cl:list args))
(defun cons (&rest args) (apply #'cl:cons args))
(defun append (&rest args) (apply #'cl:append args))

然后,在 REPL 中,转到该包:

CL-USER> (in-package :so)

#<PACKAGE "SO">

要求追踪这些功能:

SO> (trace list append cons) ;; the shadowing ones
(LIST CONS APPEND)

现在,您可以直接输入cal 的值,但这次使用的符号是我们要求跟踪的符号。

SO> (list (append (list (cons (list 1 2) (cons 3 '())))
                (list (cons 4 (cons 5 '())))) 
        6 
        7)

环境然后评估表单并打印每个函数的调用方式以及它返回的结果。

  0: (SO::LIST 1 2)
  0: LIST returned (1 2)
  0: (SO::CONS 3 NIL)
  0: CONS returned (3)
  0: (SO::CONS (1 2) (3))
  0: CONS returned ((1 2) 3)
  0: (SO::LIST ((1 2) 3))
  0: LIST returned (((1 2) 3))
  0: (SO::CONS 5 NIL)
  0: CONS returned (5)
  0: (SO::CONS 4 (5))
  0: CONS returned (4 5)
  0: (SO::LIST (4 5))
  0: LIST returned ((4 5))
  0: (SO::APPEND (((1 2) 3)) ((4 5)))
  0: APPEND returned (((1 2) 3) (4 5))
  0: (SO::LIST (((1 2) 3) (4 5)) 6 7)
  0: LIST returned ((((1 2) 3) (4 5)) 6 7)

((((1 2) 3) (4 5)) 6 7)

可视化为 cons 单元格

将列表视为 cons-cells 链会​​很有帮助,即将 (a b c) 转换为 (a . (b . (c . nil)))。让我们定义一个辅助函数:

(defun consprint (x)
  (if (consp x)
    (format nil
            "(~a . ~a)" 
            (consprint (car x))
            (consprint (cdr x)))
    (prin1-to-string x)))

结果如下:

SO> (consprint '((((1 2) 3) (4 5)) 6 7))
"((((1 . (2 . NIL)) . (3 . NIL)) . ((4 . (5 . NIL)) . NIL)) . (6 . (7 . NIL)))"

绘制:一种术语重写方法

使用递归、自下而上的方法来绘制它。

定义。:这里我将 leaf 定义为在其 CAR 和 CDR 槽中都有原子的 cons-cell:例如(0 . NIL)(X . Y) 都是叶子,但不是 ((0 . 1) . 2)。请注意,这包括不正确的列表,当我用符号替换子项时,我依靠这些列表来解释绘图方法。

((((1 . (2 . NIL)) . (3 . NIL)) . ((4 . (5 . NIL)) . NIL)) . (6 . (7 . NIL)))
        ^^^^^^^^^    ^^^^^^^^^          ^^^^^^^^^                 ^^^^^^^^^

上面我在所有叶子上加了下划线:你可以很容易地画出这些盒子,并用符号(A、B、...)标记它们。

下面我将原始单元格替换为其相关框的名称,并再次在新叶子下划线:

((((1 . A) . B) . ((4 . C) . NIL)) . (6 . D))
   ^^^^^^^         ^^^^^^^           ^^^^^^^

然后,当有一个符号代表一个盒子时,画一个指向那个盒子的箭头。例如,您定义了一个名为 E 的框,对应于 (1 . A),因此您绘制 [1/x] 并将 x 连接到框 A

您获得:

(((E . B) . (F . NIL)) . G)

现在,考虑(E . B):它的汽车是一个符号,因此您需要绘制的框没有任何价值,但是从 CAR 插槽指向E 单元格的向外箭头(就像它的 CDR 指向B)。 你重复这个过程直到终止。剩下的就是视觉布局,通常情况下,只是原子链表的盒子是水平布局的。

直接从表达式中绘制

大概原始练习希望您直接从原始表达式中绘制方框。可以像上面那样做,从叶表达式向上工作,并用表示现有框的符号替换它们的值。

  • 一个 cons 直接映射到一个框。
  • 列表只是对 cons 的重复应用,您可以通过绘制与元素一样多的框来快速完成。
  • 实际上,append 会复制参数中除最后一个列表之外的所有列表,但在绘制时,您可以“改变”现有框。对于每个现有的框,继续跟踪 CDR,直到到达 cdr 中没有箭头的框,然后将该框链接到参数中的下一个框,从而将不同的框链接在一起。

绘制实际的纯函数式 append 来看看结构共享和垃圾回收是如何工作的可能会很有趣。

【讨论】:

    【解决方案3】:

    从代码开始,您可以从顶层向下工作。给定:

    (define cal 
      (list (append (list (cons (list 1 2) (cons 3 '())))
                    (list (cons 4 (cons 5 '())))) 
            6 
            7))
    

    你可以看到第一层是一个包含三个元素的列表:一个复杂的对象,一个 6 和一个 7。这可以用三个 cons 单元格来表示:

    现在你只需要弄清楚第一个 cons 单元格的 car 指向什么。回顾代码,这指向一个由两个列表组成的列表,附加在一起。如果您查看括号,您可以看到对list 的第一次调用只使用一个参数,因此这将创建一个包含一个元素的列表。 当添加两个列表时,关闭第一个列表的 nil 将替换为指向第二个列表前面的指针,因此:

    要附加的两个列表中的第一个是由以下代码创建的:

    (list (cons (list 1 2) (cons 3 '())))
    

    这里,列表(1 2) 被转换到列表(3) 的前面以创建一个新列表。这是一个包含两个元素的列表,其中列表的car 指向列表(1 2),而列表的cdr(3)。我们可以继续为这一层绘制方框:

    由于最后一层的car只是指向列表(1 2),我们可以填写:

    现在,append 函数的第二个参数也是一个只包含一个元素的列表,所以:

    那个单一的元素是(cons 4 (cons 5 '()))的结果,也就是列表(4 5),所以我们最终可以通过从最后一个cons单元的car指向这个列表来完成我们的箱形图:

    【讨论】:

      【解决方案4】:

      尝试从内到外开始,就像口译员那样。为(cons 3 '()) 绘制图表 - 很简单,对吧?现在,应该有什么指向吗?没错,就是(cons (list 1 2) (cons 3 '()))的cdr。所以,当你为那个更大的表达式画图时,确保它的 cdr 指向你画的第一个子图。要完成这个更大的表达式,您还需要为(list 1 2) 绘制图表 - 就像您开始的地方一样简单。

      从那里向外工作 - append 操作是最棘手的部分,但您链接的说明解释了 append 的工作原理。

      【讨论】:

        【解决方案5】:

        忘记列表。没有列表。只有对。

        (define cal 
          (list (append (list (cons (list 1 2) (cons 3 '())))
                        (list (cons 4 (cons 5 '())))) 
                6 
                7))
        =
        (define NIL '())
        (define A (cons 1 (cons 2 NIL)))  ; (list 1 2)
        (define B (cons 3 NIL))           ; (cons 3 '())
        (define C (cons 5 NIL))           ; (cons 5 '())
        (define cal 
          (list (append (list (cons A B))
                        (list (cons 4 C))) 
                6 
                7))
        =
        (define NIL '())
        (define A (cons 1 (cons 2 NIL)))  
        (define B (cons 3 NIL))           
        (define C (cons 5 NIL))           
        (define D (cons A B))
        (define E (cons 4 C))
        (define cal 
          (list (append (list D)
                        (list E)) 
                6 
                7))
        =
        (define NIL '())
        (define A (cons 1 (cons 2 NIL)))  
        (define B (cons 3 NIL))           
        (define C (cons 5 NIL))           
        (define D (cons A B))
        (define E (cons 4 C))
        (define F (list D E))             ; (append (list D) (list E)) 
        (define cal 
          (list F 
                6 
                7))
        =
        (define NIL '())
        (define A (cons 1 (cons 2 NIL)))  
        (define B (cons 3 NIL))           
        (define C (cons 5 NIL))           
        (define D (cons A B))
        (define E (cons 4 C))
        (define F (cons D (cons E NIL)))  ; (list D E)
        (define cal 
          (cons F 
                (cons 6 
                      (cons 7 NIL))))
        

        每个cons 都是一个盒子。每个名称都是一个指针。

        仅此而已。

        【讨论】:

        • 完成这项工作,继续删除嵌套框,同时给它们命名(如答案所示),直到没有未命名的嵌套框。
        猜你喜欢
        • 1970-01-01
        • 2011-06-21
        • 2023-03-30
        • 1970-01-01
        • 2011-09-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多