Lisp - 标志(bandera)不起作用

Lisp - Flag(bandera) don't funtion

我正在尝试编写一个函数来确定一个单词是否为回文。我做了这个,但它总是 returns "Is not a palindrome"。我不知道发生了什么。

(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"))
    )   
)

我该如何解决这个问题?

循环问题

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

进行修改

死循环问题

但是假设你修正了你的测试,让它变成 (< i j),那么你的循环就变成了一个无限循环,因为你 never 既不改变 i 也不改变j 在循环体中。 (+ i 1)(- j 1) 这两个表达式只计算下一个索引,但不更改现有绑定。您必须像上面那样使用 setq

无效使用 SETQ

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

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

布尔逻辑

不要使用 01 作为你的标志,Lisp 使用 nil 作为布尔运算符的 false,其他一切都为 true。

混淆测试

在循环体内,你先写:

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

然后你写:

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

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

风格

  • 您通常不想从实用函数中打印东西。 您应该只 return 布尔结果。

  • 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是一个循环变量,从max
  • 开始递减
  • while是终止测试
  • always 在每次循环执行时评估一个表单,并检查它是否始终为真(非零)。

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

使用:

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

[这个函数会比你的代码更有效率 and it returns T if s is palindromic, else NIL.] (不正确,它只是节省了你的写作工作量,但它比使用 loop 的过程效率低.)

详细版本为:

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

写下你想要的答案但是 returns TNIL.

返回 TNIL 的测试函数的命名约定是 'predicate' 的名称以 p 结尾。

反向函数的性能不如@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 的评论!