具有不同相等谓词的 Lisp 案例
Lisp case with different equality predicate
作为 Tic Tac Toe 游戏机器人的一部分,我需要一个函数来计算方块与点的组合。代码看起来像这样:
(case combination
("EEEEE" 0)
("EEEEP" 1)
("EEEPE" 1)
("EEEPP" 2)
("EEPEE" 1)
("EEPEP" 2)
("EEPPE" 2)
("EEPPP" 3)
("EPEEE" 1)
("EPEEP" 2)
("EPEPE" 2)
("EPEPP" 3)
("EPPEE" 2)
("EPPEP" 3)
("EPPPE" 3)
("EPPPP" 4)
("PEEEE" 1)
("PEEEP" 2)
("PEEPE" 2)
("PEEPP" 3)
("PEPEE" 2)
("PEPEP" 3)
("PEPPE" 3)
("PEPPP" 4)
("PPEEE" 2)
("PPEEP" 3)
("PPEPE" 3)
("PPEPP" 4)
("PPPEE" 3)
("PPPEP" 4)
("PPPPE" 4)
("PPPPP" 5))
(这里不是讨论这种方法的价值的地方,因为它被用于与问题无关的原因)
问题是 case 使用的谓词对于不是同一对象的相同字符串 return 不成立(很难找到它是 eq 还是 eql)。你怎么能改变它?
编辑:我通过将字符串转换为相应的二进制数解决了原始问题,可以使用 eql 进行比较或用作列表中的索引。
使用 alexandria
库中的 alexandria:switch
,可从 quicklisp 获得。
(switch (combination :test #'string=)
("FOO" …)
…)
您的代码只计算 (count #\P combination)
。
通常我会将字符串转换成数字并用它来计算。使用 LOGCOUNT
获取位或其他内容。即使我使用类似 CASE
的大型开关,我也会将字符串一次转换为数字,而不是进行大量的字符串比较。
你可以写一个宏:
(defmacro string-case (key &rest forms)
(let ((k (gensym "KEY")))
`(let ((,k ,key))
(cond
,@(loop for (str . body) in forms
collect `((string= ,k ,str) ,@body))))))
然后像案例一样使用它。请注意,此宏将一次检查每个子字符串(在您的情况下最多 32 个分支),这比查看第一个字符并决定要做什么,然后查看下一个字符等效率低(5 -10 个分支在你的情况下),这比你实际打算做的事情效率低(例如计算 #\P
)(这可以用 5-6 个容易预测的分支来完成,或者可能是 10 个取决于实现)。在这些选项中,第二个生成的代码最多,然后是第一个,然后是第三个。
另一种方法是将 combination
转换为符号。生成的代码将如下所示:
(case (intern combination)
(EEEEE 0)
(EEEEP 1)
(EEEPE 1)
...)
但你需要记住 intern
在当前包 (*package*
) 的上下文中在运行时运行,这意味着如果这是在外部调用的函数的一部分定义它的包,它将不起作用。有两种方法可以解决这个问题(基本上,一种方法的两种变体):包中的 intern ((intern combination <your-package>)
) 或 intern 作为关键字。在后一种情况下,整个表单将如下所示:
(case (intern combination :keyword)
(:EEEEE 0)
(:EEEEP 1)
(:EEEPE 1)
...)
另外值得注意的可能是性能方面的考虑。它们不应该成为问题,因为尽管 intern
ing 是一项相当繁重的操作,但最初,当它在一个已经驻留的符号上重复调用时,它基本上只是一个字典查找。
另一种解决方案可能是将规则定义为列表,并在列表中搜索匹配的字符串。
(defun match-combination (combination)
(let ((rules '(("EEEEE" 0)
("EEEEP" 1)
("EEEPE" 1)
...)))
(cadr (find combination rules :key #'car :test #'string=))))
作为 Tic Tac Toe 游戏机器人的一部分,我需要一个函数来计算方块与点的组合。代码看起来像这样:
(case combination
("EEEEE" 0)
("EEEEP" 1)
("EEEPE" 1)
("EEEPP" 2)
("EEPEE" 1)
("EEPEP" 2)
("EEPPE" 2)
("EEPPP" 3)
("EPEEE" 1)
("EPEEP" 2)
("EPEPE" 2)
("EPEPP" 3)
("EPPEE" 2)
("EPPEP" 3)
("EPPPE" 3)
("EPPPP" 4)
("PEEEE" 1)
("PEEEP" 2)
("PEEPE" 2)
("PEEPP" 3)
("PEPEE" 2)
("PEPEP" 3)
("PEPPE" 3)
("PEPPP" 4)
("PPEEE" 2)
("PPEEP" 3)
("PPEPE" 3)
("PPEPP" 4)
("PPPEE" 3)
("PPPEP" 4)
("PPPPE" 4)
("PPPPP" 5))
(这里不是讨论这种方法的价值的地方,因为它被用于与问题无关的原因)
问题是 case 使用的谓词对于不是同一对象的相同字符串 return 不成立(很难找到它是 eq 还是 eql)。你怎么能改变它?
编辑:我通过将字符串转换为相应的二进制数解决了原始问题,可以使用 eql 进行比较或用作列表中的索引。
使用 alexandria
库中的 alexandria:switch
,可从 quicklisp 获得。
(switch (combination :test #'string=)
("FOO" …)
…)
您的代码只计算 (count #\P combination)
。
通常我会将字符串转换成数字并用它来计算。使用 LOGCOUNT
获取位或其他内容。即使我使用类似 CASE
的大型开关,我也会将字符串一次转换为数字,而不是进行大量的字符串比较。
你可以写一个宏:
(defmacro string-case (key &rest forms)
(let ((k (gensym "KEY")))
`(let ((,k ,key))
(cond
,@(loop for (str . body) in forms
collect `((string= ,k ,str) ,@body))))))
然后像案例一样使用它。请注意,此宏将一次检查每个子字符串(在您的情况下最多 32 个分支),这比查看第一个字符并决定要做什么,然后查看下一个字符等效率低(5 -10 个分支在你的情况下),这比你实际打算做的事情效率低(例如计算 #\P
)(这可以用 5-6 个容易预测的分支来完成,或者可能是 10 个取决于实现)。在这些选项中,第二个生成的代码最多,然后是第一个,然后是第三个。
另一种方法是将 combination
转换为符号。生成的代码将如下所示:
(case (intern combination)
(EEEEE 0)
(EEEEP 1)
(EEEPE 1)
...)
但你需要记住 intern
在当前包 (*package*
) 的上下文中在运行时运行,这意味着如果这是在外部调用的函数的一部分定义它的包,它将不起作用。有两种方法可以解决这个问题(基本上,一种方法的两种变体):包中的 intern ((intern combination <your-package>)
) 或 intern 作为关键字。在后一种情况下,整个表单将如下所示:
(case (intern combination :keyword)
(:EEEEE 0)
(:EEEEP 1)
(:EEEPE 1)
...)
另外值得注意的可能是性能方面的考虑。它们不应该成为问题,因为尽管 intern
ing 是一项相当繁重的操作,但最初,当它在一个已经驻留的符号上重复调用时,它基本上只是一个字典查找。
另一种解决方案可能是将规则定义为列表,并在列表中搜索匹配的字符串。
(defun match-combination (combination)
(let ((rules '(("EEEEE" 0)
("EEEEP" 1)
("EEEPE" 1)
...)))
(cadr (find combination rules :key #'car :test #'string=))))