计算列表结构中的原子数

时间:2014-03-10 12:57:57

标签: list scheme racket

我需要找到给定输入有多少元素。输入可以是列表或符号,例如:

  • 'a => 1个元素
  • '(3 . 4) => 2个元素
  • '(a b . c) => 3个元素
  • '((a b . c) 3 . 4) => 5个元素

一个问题是,当我通过输入时,每个元素都可以是它自己的列表,或者一对(刚开始学习方案,所以我现在的工具主要是car / cdr),所以,什么时候应该停止循环?当if (null? x)条件为真时?或者当if (null? (car x))为真时?

2 个答案:

答案 0 :(得分:2)

您的问题的标题应该是如何计算列表结构中的原子。 SE上有很多关于它的问题。 方法如下:

  1. 如果元素是一对,则结果将是应用于同一过程的carcdr的总和。
  2. 其他长度为1
  3. 修改

    以上算法为代码:

    (define (count-all-atoms x)
      (if (pair? x)
          (+ (count-all-atoms (car x))
             (count-all-atoms (cdr x)))
          1))
    

    评论我所获得的评论实际上有两种方法可以实现这一点,所有这些方法都会给出OP的例子的正确答案。这一切都与我们如何解释()有关。

    取决于'(())应计为0,1或2个元素。零,因为它是所有没有任何原子的列表,1因为它的一个列表有一个空元素(不计算空终止符)或2,因为它与带有两个空元素((() ()))的点对相同。这是我的文字描述的最后一个,但这是另外两种方式:

    ;; count (()) and () as 0
    ;; no nil value is counted as it is a list without elements
    (define (count-non-nil-atoms x)
      (if (pair? x) 
          (+ (count-non-nil-atoms (car x)) 
             (count-non-nil-atoms (cdr x)))
          (if (null? x)
              0 
              1)))
    
    ;; count (()) as 1 and () as 0
    ;; ie. count only nil as atom in car position but not as a list terminator
    ;; probably most common
    (define (count-atoms x)
      (if (pair? x)
          (let ((a (car x)) (d (cdr x)))        
            (+  (if (null? a) 1 (count-atoms a))
                (count-atoms d)))
          (if (null? x) 0 1)))
    

答案 1 :(得分:1)

在Common Lisp中,原子是不是一对的任何东西,你可以检查某个东西是否是具有atom函数的原子。如果某个东西不是原子,那么它就是一对,这意味着你可以用它来调用carcdr来检索该对的部分。因此,一个简单的解决方案就是:

(defun count-atoms (object)
  (if (atom object) 1
      (+ (count-atoms (car object))
         (count-atoms (cdr object)))))

请注意,这会计算正确列表的nil终结符,所以

(a b . c)   ;=> 3
(a b . nil) ;=> 3, even though (a b . nil) == (a b)

但这似乎与您对计算 atoms 的描述一致,而不是列表中的元素。但是,此实现确实会占用堆栈空间,因此您可能会遇到非常大的结构问题。您可以使用延续传递方法,而不是支持尾调用优化的Lisp实现可以使用常量堆栈空间:

(defun count-atoms (object)
  (labels ((kount (object k)
             (if (atom object)
                 (funcall k 1)
                 (kount (car object)
                        (lambda (l)
                          (kount (cdr object)
                                 (lambda (r)
                                   (funcall k (+ l r)))))))))
    (kount object 'identity)))

继续传递样式避免了使用大量的堆栈空间(如果实现优化了尾部调用),但它并不是那么透明(除非你已经习惯了那种风格)。我们可以使用显式堆栈和循环来获得更有可能使用常量堆栈空间的东西:

(defun count-atoms (object)
  (do ((objects (list object)) (natoms 0))
      ((endp objects) natoms)
    (let ((object (pop objects)))
      (cond
        ((atom object) (incf natoms))
        (t (push (car object) objects)
           (push (cdr object) objects))))))

现在我们将它作为do循环,很容易看出我们如何使用尾递归来编写它(如果你在Scheme中工作,你可能会做什么):

(defun count-atoms (object)
  (labels ((kount (objects natoms)
             (cond
               ((endp objects)
                natoms)
               ((atom (first objects))
                (kount (rest objects) (1+ natoms)))
               (t 
                (kount (list* (car (first objects))
                              (cdr (first objects))
                              (rest objects))
                       natoms)))))
    (kount (list object) 0)))