Common Lisp函数问题:如何返回列表中出现n次的所有元素
问题分析与解决方案
你遇到的问题很明确:原函数在找到第一个符合条件的元素后就立刻终止循环返回了,所以没法收集所有出现次数等于nb的元素。
原函数的问题点
看你写的代码:
(defun count-list (lst nb) (loop for x in lst do (if (eq (count x lst) nb) (return x) ) ) )
这里的(return x)是loop里的终止性返回——一旦找到第一个满足(count x lst) = nb的元素,loop就会立刻停止并返回这个元素,后面的元素根本没机会被检查。这就是为什么调用(count-list lst 1)只返回a,而不是所有出现1次的元素。
改进方案:收集所有符合条件的元素
我们需要把“找到第一个就返回”改成“收集所有符合条件的元素”,同时要注意避免重复收集同一个元素(比如原列表里的b出现3次,如果直接循环每个元素收集,会得到(b b b),这显然不是我们要的)。
方案1:先去重再筛选
最直观的方式是先获取列表中的唯一元素,再逐个检查它们的出现次数,符合条件的就收集到结果列表里:
(defun count-list (lst nb) (loop for x in (remove-duplicates lst) when (= (count x lst) nb) collect x))
测试一下:
- 执行
(setq lst '(a b b b c))后,(count-list lst 3)返回(b),符合你的需求; (count-list lst 1)返回(a c)(这里纠正一下你的笔误:原列表里b出现3次,所以出现1次的是a和c哦)。
方案2:跟踪已处理元素(避免去重函数的依赖)
如果你不想用remove-duplicates,可以自己维护一个“已处理元素”的列表,确保每个元素只被检查一次:
(defun count-list (lst nb) (let ((seen nil)) (loop for x in lst unless (member x seen) do (push x seen) when (= (count x lst) nb) collect x)))
这个逻辑和方案1本质一样,只是手动跟踪已处理的元素,避免重复检查。
关键知识点
loop的return子句是立即终止循环并返回值,适合找单个元素的场景;loop的collect子句会把符合条件的元素依次收集到一个列表里,最后返回这个列表,适合需要多个结果的场景;- 一定要去重,否则同一个元素会被多次收集(比如原列表里的
b会被循环三次,每次都符合条件,导致结果里有三个b)。
内容的提问来源于stack exchange,提问作者Nielwig
相关产品推荐
相关产品推荐

