【问题标题】:Lisp - Flag(bandera) don't funtionLisp - 标志(bandera)不起作用
【发布时间】:2018-11-08 18:14:31
【问题描述】:

我正在尝试编写一个函数来确定一个单词是否是回文。我做了这个,但它总是返回“不是回文”。我不知道发生了什么。

(defun palindromo (X)
    (setq i 0)
    (setq j (- (length X) 1))
    (setq bandera 0)
    (loop while (< j i)
        do
        (when (char= (char X i) (char X j))
            (+ i 1)
            (- j 1)
            (setq bandera 1))
        (unless (char/= (char X i) (char X j))
            (setq bandera 0)

        )
    )
    (cond
    ((equal 0 bandera) (write "Is not a palindrome"))
    ((equal 1 bandera) (write "Is a palindrome"))
    )   
)

我该如何解决这个问题?

【问题讨论】:

    标签: loops common-lisp conditional-statements palindrome clisp


    【解决方案1】:

    实际上,不需要外部定义的循环来判断字符串是否为回文。 [ 备注:嗯,我一开始也是这么想的。但正如@coredump 和@jkiiski 指出的那样,reverse 函数会减慢该过程,因为它会复制整个字符串一次。 ]

    用途:

    (defun palindromep (s)
      (string= s (reverse s)))
    

    [ 这个函数会比你的代码更有效率 如果 s 是回文,则返回 T,否则返回 NIL。](不正确,它只会节省您的写作工作量,但效率低于使用 loop 的过程。)

    一个详细的版本是:

    (defun palindromep (s)
       (let ((result (string= s (reverse s))))
         (write (if result
                    "Is a palindrome"
                    "Is not a palindrome"))
         result))
    

    写出你想要的答案,但返回TNIL

    返回TNIL 的测试函数的命名约定是以p 结尾的“谓词”。

    reverse 函数的性能不如@coredump 建议的 while 循环

    这是我的初学者尝试测试速度[不推荐]:

    ;; Improved loop version by @coredump:
    
    (defun palindromep-loop (string)
      (loop with max = (1- (length string))
            for i from 0
            for j downfrom max
            while (< i j)
            always (char= (char string i)
                          (char string j))))
    
    ;; the solution with reverse
    (defun palindromep (s)
      (string= s (reverse s)))
    
    ;; the test functions test over and over the same string "abcdefggfedcba"
    ;; 10000 times over and over again 
    ;; I did the repeats so that the measuring comes at least to the milliseconds
    ;; range ... (but it was too few repeats still. See below.)
    
    (defun test-palindrome-loop ()
      (loop repeat 10000
            do (palindromep-loop "abcdefggfedcba")))
    
    (time (test-palindrome-loop))
    
    (defun test-palindrome-p ()
      (loop repeat 10000
            do (palindromep "abcdefggfedcba")))
    
    (time (test-palindrome-p))
    
    ;; run on SBCL
    [55]> (time (test-palindrome-loop))
    Real time: 0.152438 sec.
    Run time: 0.152 sec.
    Space: 0 Bytes
    NIL
    [56]> (time (test-palindrome-p))
    Real time: 0.019284 sec.
    Run time: 0.02 sec.
    Space: 240000 Bytes
    NIL
    
    ;; note: this is the worst case that the string is a palindrome
    ;; for `palindrome-p` it would break much earlier when a string is 
    ;; not a palindrome!
    

    这是@coredump 测试功能速度的尝试:

    (lisp-implementation-type)
    "SBCL"
    
    (lisp-implementation-version)
    "1.4.0.75.release.1710-6a36da1"
    
    (machine-type)
    "X86-64"
    
    (defun palindromep-loop (string)
      (loop with max = (1- (length string))
            for i from 0
            for j downfrom max
            while (< i j)
            always (char= (char string i)
                          (char string j))))
    
    (defun palindromep (s)
      (string= s (reverse s)))
    
    (defun test-palindrome-loop (s)
      (sb-ext:gc :full t)
      (time
       (loop repeat 10000000
             do (palindromep-loop s))))
    
    (defun test-palindrome-p (s)
      (sb-ext:gc :full t)
      (time
       (loop repeat 10000000
             do (palindromep s))))
    
    (defun rand-char ()
      (code-char
       (+ #.(char-code #\a)
          (random #.(- (char-code #\z) (char-code #\a))))))
    
    (defun generate-palindrome (n &optional oddp)
      (let ((left (coerce (loop repeat n collect (rand-char)) 'string)))
        (concatenate 'string
                     left
                     (and oddp (list (rand-char)))
                     (reverse left))))
    
    (let ((s (generate-palindrome 20)))
      (test-palindrome-p s)
      (test-palindrome-loop s))
    
    Evaluation took:
      4.093 seconds of real time
      4.100000 seconds of total run time (4.068000 user, 0.032000 system)
      [ Run times consist of 0.124 seconds GC time, and 3.976 seconds non-GC time. ]
      100.17% CPU
      9,800,692,770 processor cycles
      1,919,983,328 bytes consed
    
    Evaluation took:
      2.353 seconds of real time
      2.352000 seconds of total run time (2.352000 user, 0.000000 system)
      99.96% CPU
      5,633,385,408 processor cycles
      0 bytes consed
    

    我从中学到的: - 更严格地测试,尽可能多地重复(秒范围) - 随机生成,然后并行测试

    非常感谢@coredump 的好例子!对于@jkiiski 的评论!

    【讨论】:

    • @Gwang-JinKim REVERSE 复制整个字符串,这比使用两个索引循环并在第一个不匹配时退出的效率要低得多。
    • @Gwang-JinKim 循环版本需要更少的测试。 STRING= 在最坏的情况下会测试每个字母,而循环版本可以在两个索引交叉时停止(因此它只测试字符串的一半与另一半)
    • @Gwang-JinKim 如果我在我的机器上对它们进行计时,循环版本只需要逆向版本的一半时间和一半的周期。您可能会得到不同的结果,因为重复次数太少以至于结果实际上是随机的。尝试将重复从10000 更改为100000000,差异应该会变得更加清晰。
    • 只有 10000 次迭代,我得到了大约 1 毫秒,这不够精确。我尝试了更长的回文(非常量),更多的重复并在测试前调用 gc:pastebin.com/raw/qCb5PEuh
    • @Gwang-JinKim 没问题,这对我来说似乎违反直觉。有时编译器会做一些奇怪的事情(例如 C 编译器),所以如果你的常量字符串测试在某些情况下被编译为常量表达式,我不会感到完全惊讶。此外,统计数据也很困难。
    【解决方案2】:

     循环问题

    您的循环终止测试是while (&lt; j i),但您之前将ij 分别设置为第一个和最后一个字符的索引。这意味着(&lt;= i j)。你永远不会执行循环体,bandera 永远不会从它的初始值 0 修改。

    无限循环问题

    但是假设您修复了您的测试,使其变为(&lt; i j),那么您的循环就变成了一个无限循环,因为您永远不会在循环体中改变ij . (+ i 1)(- j 1) 这两个表达式仅计算下一个索引,但不更改现有绑定。您必须使用setq,就像您在上面所做的那样。

    SETQ 使用无效

    顺便说一句,你不能用setq 引入变量:当试图设置一个未定义的变量时会发生什么是未定义的。您可以使用defvardefparameter 引入全局变量,并使用letlet* 和循环关键字with 等引入局部变量。

    我假设您的 Common Lisp 实现在您执行或编译 (setq i 0) 和其他赋值时隐式定义了全局变量。但这远非理想,因为现在您的函数取决于全局状态并且不可重入。如果你从不同的线程调用palindromo,所有的全局变量都会被同时修改,这会导致错误的结果。更好地使用局部变量。

    布尔逻辑

    不要将01 用作您的标志,Lisp 将nil 用作布尔运算符,而其他一切都用作其布尔运算符。

    令人困惑的测试

    在循环体中,你先写:

     (when (char= (char X i) (char X j)) ...)
    

    然后你写:

     (unless (char/= (char X i) (char X j)) ...)
    

    两者都测试相同的东西,第二个涉及双重否定(除非不等于),这很难阅读。

    风格

    • 您通常不希望从实用程序函数中打印内容。 您可能应该只返回一个布尔结果。

    • X 的名字有点不清楚,我会用string

    • 尝试使用传统的方式来格式化您的 Lisp 代码。它有助于使用自动缩进代码的编辑器(例如 Emacs)。此外,不要将悬空括号留在自己的行中。

    重写

    (defun palindromep (string)
      (loop with max = (1- (length string))
            for i from 0
            for j downfrom max
            while (< i j)
            always (char= (char string i)
                          (char string j))))
    
    • 按照惯例,我在palindrome 中添加了一个p,因为它是一个谓词。
    • 循环中的with max = ... 定义了一个循环变量,该变量保存最后一个字符的索引(如果string 为空,则为-1)。
    • i 是一个循环变量,从 0 开始递增
    • j 是一个循环变量,从最大值开始递减
    • while是一个终止测试
    • always 在每次执行循环时评估一个表单,并检查它是否始终为真(非零)。

    【讨论】:

    • 我很感谢你@coredump!感谢您花时间解释我所有的错误或不是很好的做法。我明白你写的一切。再次感谢你!你好! :)
    • @CinthiaLima 没问题,希望对您有帮助
    猜你喜欢
    • 2015-09-28
    • 1970-01-01
    • 1970-01-01
    • 2023-03-26
    • 1970-01-01
    • 2011-10-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多