【问题标题】:Can someone breakdown what's happening in this piece of code?有人可以分解这段代码中发生的事情吗?
【发布时间】:2023-04-10 01:52:01
【问题描述】:

我对 LISP 编程非常陌生,而且我在语法方面遇到了困难。以下代码来自我的笔记,我知道它的作用,但我非常感谢逐行细分以更好地了解这里发生的事情。 “when”循环似乎很容易理解,但特别是我很难理解“do”循环中的前 3 行。另外我不确定为什么在when 循环的最后一行使用了(:= acc (1+ acc)

(defun count-lower-case-vowels (str)
  (do ((i 0 (1+ i))
       (acc 0)
       (len (length str)))
      ((= i len) acc)
    (when (or (equal (aref str i) #\a) (equal (aref str i) #\e)
              (equal (aref str i) #\i) (equal (aref str i) #\o)
              (equal (aref str i) #\u))
      (:= acc (1+ acc)))))

【问题讨论】:

  • 发生了什么是某人的“TAB”键坏了。我建议使用一个很好的代码编辑器,它会为你缩进。它会让很多事情变得更清楚。
  • @SilvioMayolo 你推荐什么代码编辑器?
  • @snk Emacs 和 Vim 都会正确缩进 Lisp 代码。

标签: string lisp common-lisp do-loops


【解决方案1】:

请注意,您发布的代码是旧式的。

现在可以用loopfind 写得更短:

(defun count-lower-case-vowels (string)
  (loop for c across string
        count (find c "aeiou")))

【讨论】:

    【解决方案2】:

    do 是一个需要 3 个参数的宏:

    (do ((i 0 (1+ i))
         (acc 0)
         (len (length str))) ;; first argument
        ((= i len) acc) ;; Second one
      (when ...) ;; third
    )
    
    • 第一个参数本身就是一个列表,该元素的每个元素都具有以下形式:

    <var-name> <var-initial-value> <var-next-value>

    在你的例子中,(i 0 (1+ i)) 的形式意味着在do 宏的主体中(第三个参数中的=),你引入了一个新的局部变量i。它从值 0 开始,在循环的每一步,它都会更新为值 (1+ i)它会增加 1)。

    您会看到此列表的第二个元素是acc 0,其中没有<var-next-value>。这意味着acc不会被宏自动更新,它的值只会根据body中所做的事情而改变。

    • 第二个参数是一个或(可选)两个元素的列表<condition> <return-val> 第一个<condition> 说明何时停止迭代:一旦计算结果为真,宏就会停止。它在每次迭代之前被评估。第二个可选部分是一个表单,说明do 表单返回的内容。默认情况下,它返回nil,但如果您在此处指定一个表单,则会在退出循环之前对其进行评估,并返回return-val

    • 如果condition 为假,第三个参数只是将在每个步骤中执行的表单列表。

    【讨论】:

      【解决方案3】:

      我非常支持大量额外的空白,以实现视觉代码对齐(在 2D 中,是的,就像在一张纸上一样)以提高可读性:

      (defun count-lower-case-vowels (str)
        (do (  (i    0      (1+ i) )         ; loop var `i`, its init, step exprs
               (acc  0             )         ; loop var `acc`, its init expr
               (len  (length str)  )  )      ; loop var `len`, its init expr
            ((= i len)                       ; loop stop condition
               acc)                          ; return value when loop stops
          (if                                ; loop body: if
              (find  (aref str i)  "aeiou")  ;              test
            (setf  acc  (1+ acc)))))         ;              consequent
      

      这样更好吗?

      这绝对不是 LISP 代码格式化的公认标准。但无论怎样使它更具可读性,我认为是最好的。

      i 的 step 表达式的含义是,在循环没有停止并且对其主体进行评估之后的每一步,都会调用 (setf i (1+ i))acclen 没有步骤表达式,因此对于它们来说,每一步都不会调用任何内容。

      至于你提到的“when循环”,它根本不是循环,也根本不是do循环的循环机制的一部分。 when 形式就像没有替代的 if 一样,它还允许在结果中包含多个语句,就像隐含的 progn

      (when test a1 a2 ...)
      ===
      (if test (progn a1 a2 ...))
      

      恰好这个循环的body 包含一个when 表单。我用等效的if 重写了它。

      【讨论】:

      • 这确实让事情变得更容易理解,但你能解释一下第一行中 (1+ i) 的意义吗?
      • 这是一个步骤表达式。这意味着在循环没有停止并且评估其主体之后的每一步,都会调用(setf i (1+ i))acclen 没有步骤表达式,因此对于它们来说,每个步骤都不会调用任何内容。
      • "...不是 LISP 代码格式的公认标准。" -- 所有这些空格让我的眼睛有点疼 ;) 实际上看起来有点难比传统格式更适合我阅读。我通常不会做很多特定于对齐的格式化,因为每当你对一些代码进行小的更改时,你都必须修改区域中的格式化;我大多只是让我的编辑做它的事。 IAC,格式是个人的,除非你在处理别人的代码;然后是家规。
      • OP 在他们的笔记中的代码中询问了:=;可能值得添加一些关于这不是 Common Lisp 函数的内容;它可能是显示代码的人正在使用的setf 的别名。
      • @adabsurdum 确实这就是我在重写时用setf 替换:= 的原因。但我觉得我必须写很多废话来解释它,提到宏等,所以我把它留在了那里。 :) 重新格式化,你的思想已经这样连接了。 :)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-02-04
      • 2015-09-14
      • 1970-01-01
      • 2020-11-03
      • 2011-08-20
      • 1970-01-01
      相关资源
      最近更新 更多