如何解决计算相等的元素
我有这个清单:
(2 2 2 2 3 4 4 5 5 5 6 7 7 7 8 8)
并想返回重复次数。结果应该是这样的:
(4 1 2 3 1 3 2)
我尝试了递归方法,但没有成功。我不确定这是正确的方法。
首先我做了一个函数来计算元素相等时的数量:
(defun count-until-dif (alist)
(1+ (loop for i from 0 to (- (length alist) 2)
while (equal (nth i alist) (nth (1+ i) alist))
sum 1)))
然后是递归函数(不起作用!):
(defun r-count-equal-elem (alist)
(cond
((NULL alist) nil)
((NULL (car alist)) nil)
((NULL (cadr alist)) nil)
((equal (car alist) (cadr alist))
(cons (count-until-dif alist) (r-count-equal-elem (cdr alist)))
)
(t (cons 1 (r-count-equal-elem (cdr alist)) )
) ) )
解决方法
这是您的函数,并带有一些注释:
(defun r-count-equal-elem (alist)
(cond
((NULL alist) nil)
;; the two tests below are not necessary in my opinion,;; the fact that the list may contain NIL elements should
;; be a separate problem,as a first draft you can avoid it
((NULL (car alist)) nil)
((NULL (cadr alist)) nil)
;; what you should be testing is if the cddr is NULL,this would
;; tell you that there is only one remaining element in the list.
((equal (car alist) (cadr alist))
;; you cons the count with a recursive result computed just
;; one place after the current one,but if you have a repetition of
;; N times value V,the recursive count will contain N-1 repetitions
;; of V,etc. you have to advance in the list by N for the recursive
;; case
(cons (count-until-dif alist) (r-count-equal-elem (cdr alist)))
)
;; this looks like a corner case that could be merged
;; with the general case above.
(t (cons 1 (r-count-equal-elem (cdr alist)) )
) ) )
另外,辅助函数有点低效:
(defun count-until-dif (alist)
;; each time you call count-until-dif you compute the length
;; of the linked list,which needs to traverse the whole list.
;; you generally do not need to know the length,you need to know
;; if there is a next element or not to guard your iteration.
(1+ (loop for i from 0 to (- (length alist) 2)
while (equal (nth i alist) (nth (1+ i) alist))
sum 1)))
我建议编写一个如下所示的函数 occur-rec
:
(defun occur-rec (list last counter result)
(if (null list)
....
(destructuring-bind (head . tail) list
(if (equal head last)
(occur-rec ... ... ... ...)
(occur-rec ... ... ... ...)))))
该函数最初使用输入列表调用,last
所见值绑定到 nil
,当前 counter
设置为零,result
为 { {1}}。
该函数的目的是通过递归调用 nil
将结果的反向构建为 result
。 occur-rec
参数表示哪个是最后看到的值,last
是最后一个值的出现次数。
注意:
- 当你调用
counter
时,它返回你想要返回的反向列表 - 反转后的第一项将始终为零,因此您需要将其丢弃。
一种幼稚的方法可能如下:
(2 2 2 2 3 4 4 5 5 5 6 7 7 7 8 8)
首先,使用返回 (remove-duplicates '(1 2 2 3))
(1 2 3)
获取唯一数字列表
(2 3 4 5 6 7 8)
然后对于每个数字,计算它们在第一个列表中出现的次数:
(let ((repetitions '()))
(dolist ((value unique-list))
(let ((count (count-if (lambda (x)
(= x value))
initial-list)))
(push count repetitions))))
,
一种直接的方法是使用 count-if
来计算列表的第一个元素出现的次数,并使用 nthcdr
来减少列表来递归输入列表。这仅适用于元素组合在一起的列表,如 OP 示例输入。
(defun count-reps (xs)
(if (endp xs)
'()
(let ((count (count-if #'(lambda (x) (eql x (car xs))) xs)))
(cons count (count-reps (nthcdr count xs))))))
CL-USER> (count-reps '(2 2 2 2 3 4 4 5 5 5 6 7 7 7 8 8))
(4 1 2 3 1 3 2)
这是一种不使用 count-if
递归构建计数列表的替代解决方案:
(defun count-reps (xs)
(if (endp xs)
'()
(let ((rest-counts (count-reps (cdr xs))))
(if (eql (car xs) (cadr xs))
(cons (1+ (car rest-counts))
(cdr rest-counts))
(cons 1 rest-counts)))))
此处的 rest-counts
表示列表其余部分的计数列表。当列表的第一个元素和列表的第二个元素为eql
时,第一个计数递增;否则遇到一个新元素,并且 1 被cons
添加到计数列表中。
您发布的解决方案从正确的方向开始,但递归函数 r-count-equal-elem
有点偏离轨道。您不需要使用 null
检查这么多情况,并且没有理由检查 equal
的元素,因为您已经在 count-until-dif
中这样做了。实际上,使用 count-until-dif
,您可以通过与上述第一个解决方案非常相似的方式来解决问题:
(defun r-count-equal-elem (alist)
(if (null alist)
'()
(let ((count (count-until-dif alist)))
(cons count
(r-count-equal-elem (nthcdr count alist))))))
,
我还会添加带有 reduce
的功能性(ish)变体:
(defun runs (data &key (test #'eql))
(when data
(flet ((add-item (res-alist x)
(if (funcall test x (caar res-alist))
(progn (incf (cdar res-alist))
res-alist)
(cons (cons x 1) res-alist))))
(nreverse (reduce #'add-item (cdr data)
:initial-value (list (cons (car data) 1)))))))
CL-USER> (runs '(2 2 2 2 3 4 4 5 5 5 6 7 7 7 8 8) :test #'=)
;;=> ((2 . 4) (3 . 1) (4 . 2) (5 . 3) (6 . 1) (7 . 3) (8 . 2))
CL-USER> (mapcar #'cdr (runs '(2 2 2 2 3 4 4 5 5 5 6 7 7 7 8 8) :test #'=))
;;=> (4 1 2 3 1 3 2)
,
使用 loop
:
(defun count-reps (list)
(loop for count from 1
for (curr next) on list
unless (eql curr next)
collect (shiftf count 0)))
文字相同;在循环中,从 1 设置一个自动递增计数器 COUNT,并选择参数列表的连续子列表的第一个 (CURR) 和第二个元素 (NEXT),忽略列表的其余部分。当第一个 (CURR) 和第二个 (NEXT) 元素不同时(或者因为不同的数字,或者我们在 NEXT 为 nil 的末尾),将 COUNT 的值存储在结果列表中,并将 COUNT 设置为 0。>
使用 mapl
的 lispier 版本,类似于 loop
的“for ... on list”服务列表的连续 cdrs:
(defun count-reps (list &aux (counter 0) (result (list)))
(mapl (lambda (head)
(incf counter)
(unless (eql (first head) (second head))
(push (shiftf counter 0) result)))
list)
(reverse result))
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。