Getting every nth atom using scheme does not pick up the last atom

吃可爱长大的小学妹 提交于 2019-12-17 14:56:41

问题


The program is suppose to pick out every third atom in a list. Notice that the last atom 'p' should be picked up, but its not. Any suggestions as to why the last atom is not being selected.

(define (every3rd lst)
 (if (or (null? lst)            
      (null? (cdr lst)))     
  '()                        
  (cons (car lst)            
        (every3rd (cdr(cdr(cdr lst)))))))


(every3rd '(a b c d e f g h i j k l m n o p))

Value 1: (a d g j m)

Thanks


回答1:


You're missing a couple of base cases:

(define (every3rd lst)
  (cond ((or (null? lst) (null? (cdr lst))) lst)
        ((null? (cdr (cdr lst))) (list (car lst)))
        (else (cons (car lst)
                    (every3rd (cdr (cdr (cdr lst))))))))

See how the following cases should be handled:

(every3rd '())
=> '()

(every3rd '(a))
=> '(a)

(every3rd '(a b))
=> '(a)

(every3rd '(a b c))
=> '(a)

(every3rd '(a b c d))
=> '(a d)

(every3rd '(a b c d e f g h i j k l m n o p))
=> '(a d g j m p)



回答2:


Fixing your code (covering the base cases)

It's worth noting that Scheme defines a number of c[ad]+r functions, so you can use (cdddr list) instead of (cdr (cdr (cdr list))):

(cdddr '(a b c d e f g h i))
;=> (d e f g h i)

Your code, as others have already pointed out, has the problem that it doesn't consider all of the base cases. As I see it, you have two base cases, and the second has two sub-cases:

  • if the list is empty, there are no elements to take at all, so you can only return the empty list.
  • if the list is non-empty, then there's at least one element to take, and you need to take it. However, when you recurse, there are two possibilies:
    • there are enough elements (three or more) and you can take the cdddr of the list; or
    • there are not enough elements, and the element that you took should be the last.

If you assume that <???> can somehow handle both of the subcases, then you can have this general structure:

(define (every3rd list)
  (if (null? list)
      '()
      (cons (car list) <???>)))

Since you already know how to handle the empty list case, I think that a useful approach here is to blur the distinction between the two subcases, and simply say: "recurse on x where x is the cdddr of the list if it has one, and the empty list if it doesn't." It's easy enough to write a function maybe-cdddr that returns "the cdddr of a list if it has one, and the empty list if it doesn't":

(define (maybe-cdddr list)
  (if (or (null? list)
          (null? (cdr list))
          (null? (cddr list)))
      '()
      (cdddr list)))
> (maybe-cdddr '(a b c d))
(d)
> (maybe-cdddr '(a b c))
()
> (maybe-cdddr '(a b))
()
> (maybe-cdddr '(a))
()
> (maybe-cdddr '())
()

Now you can combine these to get:

(define (every3rd list)
  (if (null? list)
      '()
      (cons (car list) (every3rd (maybe-cdddr list)))))
> (every3rd '(a b c d e f g h i j k l m n o p))
(a d g j m)

A more modular approach

It's often easier to solve the more general problem first. In this case, that's taking each nth element from a list:

(define (take-each-nth list n)
  ;; Iterate down the list, accumulating elements 
  ;; anytime that i=0.  In general, each
  ;; step decrements i by 1, but when i=0, i
  ;; is reset to n-1.
  (let recur ((list list) (i 0))
    (cond ((null? list) '())
          ((zero? i)    (cons (car list) (recur (cdr list) (- n 1))))
          (else         (recur (cdr list) (- i 1))))))
> (take-each-nth '(a b c d e f g h i j k l m n o p) 2)
(a c e g i k m o)

> (take-each-nth '(a b c d e f g h i j k l m n o p) 5)
(a f k p)

Once you've done that, it's easy to define the more particular case:

(define (every3rd list)
  (take-each-nth list 3))
> (every3rd '(a b c d e f g h i j k l m n o p))
(a d g j m)

This has the advantage that you can now more easily improve the general case and maintain the same interface every3rd without having to make any changes. For instance, the implementation of take-each-nth uses some stack space in the recursive, but non-tail call in the second case. By using an accumulator, we can built the result list in reverse order, and return it when we reach the end of the list:

(define (take-each-nth list n)
  ;; This loop is like the one above, but uses an accumulator
  ;; to make all the recursive calls in tail position.  When 
  ;; i=0, a new element is added to results, and i is reset to
  ;; n-1.  If i≠0, then i is decremented and nothing is added 
  ;; to the results.  When the list is finally empty, the
  ;; results are returned in reverse order.
  (let recur ((list list) (i 0) (results '()))
    (cond ((null? list) (reverse results))
          ((zero? i)    (recur (cdr list) (- n 1) (cons (car list) results)))
          (else         (recur (cdr list) (- i 1) results)))))



回答3:


It is because (null? '()) is true. you can debug what's happening with following code

(define (every3rd lst)
 (if (begin 
      (display lst)
      (newline)
      (or (null? lst)            
        (null? (cdr lst))))
  '()                        
  (cons (car lst)            
        (every3rd (cdr(cdr(cdr lst)))))))

(every3rd '(a b c d e f g h i j k l m n o p))
(newline)    
(display (cdr '(p)))
(newline)
(display (null? '()))
(newline)
(display (null? (cdr '(p))))
(newline)

this gives following result.

(a b c d e f g h i j k l m n o p)
(d e f g h i j k l m n o p)
(g h i j k l m n o p)
(j k l m n o p)
(m n o p)
(p)

()
#t
#t


来源:https://stackoverflow.com/questions/22826902/getting-every-nth-atom-using-scheme-does-not-pick-up-the-last-atom

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!