(方案)获取一个列表和 return 个包含正数、负数和零的新列表

(Scheme) Take a list and return new list with count of positive, negative, and zeros

我正在尝试接受一个列表,让它计算正数、负数和零,然后 return 一个新列表。

我在调试时唯一注意到的是列表正在迭代,但它没有使用任何条件。所以它成功地递归调用了自己,然后一旦它为空就会出错。

(define (mydisplay value)
(display value)
(newline)
#t
)

(define neg 0)
(define z 0)
(define pos 0)

  (define (posneg lst)

  (cond 
    ((NULL? lst))
    (NEGATIVE? (car lst) (+ 1 neg))
    (ZERO? (car (lst)) (+ 1 z))
    (else (+ 1 pos))
   )

  (posneg (cdr lst))
)
(mydisplay (posneg '(1 2 3 4 2 0 -2 3 23 -3)))
(mydisplay (posneg '(-1 2 -3 4 2 0 -2 3 -23 -3 0 0)))
(mydisplay (posneg '()))

好的,我最喜欢在这里应用的技术是 如意算盘,因为我从 Gerald Jay Sussman 和 Hal Abelson 那里学到了 Structure and Interpretation of Computer Programs (SICP) course. Particularly, video lecture 2B. Compound Data 你会感兴趣的.

让我们从假装 (wishing) 开始,让我们可以使用这个数据容器来保存 3 个值:一个用于 p积极因素,一项针对n消极因素,一项针对z爱欲。我们称之为 pnz.

创建其中之一的方法很简单

; construct a pnz that has 1 positive, 4 negatives, and 2 zeros
(define x (make-pnz 1 4 2))

到select正值

(positives x) ;=> 1

到select一个负值

(negatives x) ;=> 4

到select零值

(zeros x) ;=> 2

暂时忘记这些程序不存在()。相反,只是希望他们这样做并开始编写程序来解决您的问题。

我们将从一些伪代码开始

; pseudocode
define count-pnz xs
  if xs is null? return (make-pnz p n z)
  if (car xs) is positive, update the positive count by one
  if (car xs) is negative, update the negative count by one
  if (car xs) is zero, update the zero count by one
  return count-pnz (cdr xs)

好的,实际上这很简单。好吧,有一个小陷阱。注意我说的是 "update the count by one"?好吧,我们需要在某个地方存储该计数,因为过程正在迭代。让我们稍微调整一下伪代码,这次包括一个 pnz 参数来跟踪我们当前的计数

; pseudocode v2
define count-pnz xs pnz=(0 0 0)
  if xs is null? return (make-pnz p n z)
  if (car xs) is positive, nextpnz = (make-pnz p+1 n z)
  if (car xs) is negative, nextpnz = (make-pnz p n+1 z)
  if (car xs) is zero, nextpnz = (make-pnz p n z+1)
  return count-pnz (cdr xs) nextpnz

现在这个过程对我来说很有意义。在最简单的情况下,xs 是一个空列表,它将简单地 return pnz of (0 0 0)。如果 xs 有任意数量的值,它将逐个遍历列表,并递增 pnz 容器中的相应值。

将其转化为方案轻而易举

; wishful thinking
; we will define make-pnz, positives, negatives, and zeros later
(define (count-pnz xs (pnz (make-pnz 0 0 0)))
  (let [(p (positives pnz))
        (n (negatives pnz))
        (z (zeros pnz))]
    (cond [(null? xs) pnz]
          [(> (car xs) 0) (count-pnz (cdr xs) (make-pnz (+ 1 p) n z))]
          [(< (car xs) 0) (count-pnz (cdr xs) (make-pnz p (+ 1 n) z))]
          [(= (car xs) 0) (count-pnz (cdr xs) (make-pnz p n (+ 1 z)))])))

您会注意到我在这里使用了 let 以便更轻松地引用当前迭代的各个 pnz 值。这样,当我们检测到正数、负数或零时,我们可以通过简单地相应地执行 (+ 1 p)(+ 1 n)(+ 1 z) 来轻松地增加适当的值。不需要递增的值可以原样传递。

我们已经非常接近了。我们的程序在逻辑上是合理的,但我们需要先定义 make-pnzpositivesnegativeszeros,然后才能运行。顺便说一句,这种通过创建 构造函数 selectors 来定义数据对象以将使用与表示隔离开来的方法称为 数据抽象。如果您有兴趣,可以在我链接的视频中了解更多相关信息。

所以这是我们需要履行的合同

; PNZ CONTRACT
; pnz *must* behave like this
(positives (make-pnz p n z)) ⇒ p
(negatives (make-pnz p n z)) ⇒ n
(zeros     (make-pnz p n z)) ⇒ z

让我们来实施吧!

; constructor
(define (make-pnz p n z)
  (list p n z))

; positives selector
(define (positives pnz)
  (car pnz))

; negatives selector
(define (negatives pnz)
  (cadr pnz))

; zeros selector
(define (zeros pnz)
  (caddr pnz))

Pff,这很简单!使用 listcarcadrcaddr 使我们的工作变得简单,并且很容易理解 pnz 的行为方式。

事不宜迟,让我们看看这个东西的实际效果

(define answer (count-pnz '(-1 2 -3 4 2 0 -2 3 -23 -3 0 0)))
(positives answer) ; => 4
(negatives answer) ; => 5
(zeros answer)     ; => 3

好了。一厢情愿的想法和一些数据抽象来拯救。

数据抽象是一个非常强大的概念。你可能会想,"Why didn't we just use list in the count-pnz procedure instead of all of this constructor/selector ceremony?" 答案可能不是很明显,但对我来说有点太多了post。相反,我真诚地希望您查看我链接的学习资源,因为我相信它们会对您大有裨益。


@DavinTryon says "@naomik's answer could be defined in something other than a list (even just functions)."

是的,完全正确。让我们看看 make-pnzpositivesnegativeszero 以不同的方式实现。请记住,合同 仍然必须履行才能使此实施被视为有效。

; constructor
(define (make-pnz p n z)
  (λ (f) (f p n z)))

; selectors
(define (positives pnz)
  (pnz (λ (p n z) p)))

(define (negatives pnz)
  (pnz (λ (p n z) n)))

(define (zeros pnz)
  (pnz (λ (p n z) z)))

很酷。这展示了数据抽象之美。我们能够以不同的方式完全重新实现 make-pnzpositivesnegativeszeros,但是因为我们仍然履行了原始合同,我们的 count-pnz 功能根本不需要改变。

首先,让我说@naomik 的回答很棒。这是解构问题并逐步构建问题的方法。

当我第一次看到这个问题时,我最后的想法是:

How can I reduce a list of integers to the defined list '(p n z)?

所以 reduce 意味着可能使用 foldlfoldr.

这是 foldr 的示例(返回 '(p n z) 格式的列表):

(define (count-pnz xs)
  (foldr (lambda (next prev)
       (cond ((= next 0) (list (car prev) (cadr prev) (+ 1 (caddr prev))))
         ((< next 0) (list (car prev) (+ 1 (cadr prev)) (caddr prev)))
         (else (list (+ 1 (car prev)) (cadr prev) (caddr prev)))))
     '(0 0 0) xs))

解决方案的主体是我们用来减少的 lambda。本质上,这只是将 1 添加到列表的 pnz 位置(分别使用 carcadrcaddr) .

*注意,此解决方案未优化。

在计算时保留值的更好方法是制作一个包含您要保留的数据作为参数的助手。更新值与通过提供新值递归相同:

(define (pos-neg-zero lst)
  (define (helper pos neg zero lst)
    (cond ((null? lst) (pnz pos neg zero)) ; the result
          ((positive? (car lst)) (helper (+ 1 pos) neg zero (cdr lst)))
          ((negative? (car lst)) (helper pos (+ neg 1) zero (cdr lst)))
          (else (helper pos neg (+ zero 1) (cdr lst)))))
  (helper 0 0 0 lst))

我喜欢@naomik 的抽象,但是 unboxing/boxing 帮助程序中的每次迭代可能有点矫枉过正。虽然有合同很好,但 Racket 和 R6RS 都支持创建你自己的类型:

;; scheme version (r6rs) 
(define-record-type (pnz-type pnz pnz?)
  (fields 
    (immutable p pnz-p)
    (immutable n pnz-n)
    (immutable z pnz-z)))

;; racket
(struct pnz (p n z) #:transparent)

另一种方法是将值作为带有值的单独结果返回,或者它可以继续:

(define (pos-neg-zero lst . lcont)
  (define cont (if (null? lcont) values (car lcont)))
  (define (helper pos neg zero lst)
    (cond ((null? lst) (cont pos neg zero)) ; the result
          ((positive? (car lst)) (helper (+ 1 pos) neg zero (cdr lst)))
          ((negative? (car lst)) (helper pos (+ neg 1) zero (cdr lst)))
          (else (helper pos neg (+ zero 1) (cdr lst)))))
  (helper 0 0 0 lst))


(pos-neg-zero '(1 -3 0))                            ; returns 1, 1, and 1
(pos-neg-zero '(1 -3 0) pnz)                        ; returns result as an object
(pos-neg-zero '(1 -3 0) list)                       ; returns result as a list
(pos-neg-zero '(1 -3 0) (lambda (p n z) (+ p n z))) ; does something with the results 

#!racket 有可选参数,所以原型可以不需要第一个表达式来检查是否传递了额外的参数:

(define (pos-neg-zero lst (cont values))
  ...)