微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

计算相等的元素

如何解决计算相等的元素

我有这个清单:

(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 将结果的反向构建为 resultoccur-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 举报,一经查实,本站将立刻删除。