【问题标题】:Count equal elements计算相等的元素
【发布时间】:2021-01-19 08:05:41
【问题描述】:

我有这个清单:

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

【问题讨论】:

    标签: lisp common-lisp


    【解决方案1】:

    这是您的函数,并带有一些注释:

    (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 设置为零,resultnil

    该函数的目的是通过递归调用occur-rec 将结果的逆向构建到result 中。 last 参数表示哪个是最后一个值,counter 是最后一个值的出现次数。

    注意:

    • 当你调用occur-rec时,它会返回你想要返回的反向列表
    • 反转后的第一项总是为零,所以你需要丢弃它。

    【讨论】:

      【解决方案2】:

      一种直接的方法是使用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 被 consed 到计数列表中。

      您发布的解决方案从正确的方向开始,但递归函数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))))))
      

      【讨论】:

      • 哇!这正是我一直在寻找的。在这里我可以学到很多东西。谢谢!
      • 我明白了,这很好。我不知道 nthcdr 函数,并且总是只在列表中执行 cdr。
      • @adrian -- nthcdr 可以很方便;我添加了几个指向 HyperSpec 的链接。
      【解决方案3】:

      一种天真的方法可能如下:

      (2 2 2 2 3 4 4 5 5 5 6 7 7 7 8 8)
      

      首先,使用返回(1 2 3)的函数(remove-duplicates '(1 2 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))))
      

      【讨论】:

      • 非常感谢!我会研究这个解决方案。
      【解决方案4】:

      我还将使用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)
      

      【讨论】:

        【解决方案5】:

        使用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”服务于列表的连续cdr:

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

        【讨论】:

          猜你喜欢
          • 2011-12-30
          • 1970-01-01
          • 1970-01-01
          • 2022-11-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-11-24
          • 1970-01-01
          相关资源
          最近更新 更多