我试图接受一个列表,让它计算正数,负数和零,并返回一个新列表。
我在调试时唯一注意到的是列表正在迭代,但它没有使用任何条件。所以它成功地递归调用自身,然后它只是错误一旦它是空的。
(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 '()))
答案 0 :(得分:2)
好吧,我最喜欢的技术是一厢情愿,因为我是从Gerald Jay Sussman和Hal Abelson那里学到的Structure and Interpretation of Computer Programs (SICP) course。特别是video lecture 2B. Compound Data会引起你的兴趣。
让我们首先假装(希望)让我们拥有 3 值的数据容器:一个用于 p ositives,一个用于 n egatives,一个用于 z 色情。我们称之为pnz
。
创建其中之一的方法很简单
; construct a pnz that has 1 positive, 4 negatives, and 2 zeros
(define x (make-pnz 1 4 2))
选择肯定值
(positives x) ;=> 1
选择否定值
(negatives x) ;=> 4
选择零值
(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)
好吧,实际上这很直截了当。好吧,有一点小问题。请注意我说"用一个更新计数"?好吧,我们需要在某个地方存储该计数,因为该过程正在迭代。我们稍微调整一下伪代码,这次包括一个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
是一个空列表,它只会返回pnz
(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
,以便更轻松地引用当前迭代的各个p
,n
,z
值。这样,当我们检测到正数,负数或零时,我们可以通过简单地相应地执行(+ 1 p)
,(+ 1 n)
或(+ 1 z)
来轻松增加适当的值。不应该增加的值可以简单地传递给未触及的。
我们非常接近。我们的程序具有逻辑意义,但我们需要先定义make-pnz
,positives
,negatives
和zeros
才能发挥作用。顺便说一下,这种通过创建构造函数和选择器来定义数据对象以将使用与表示隔离的方法称为数据抽象。如果您有兴趣,我会在我关联的视频中了解更多相关内容。
所以我们需要完成合同
; 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,这很容易!使用list
,car
,cadr
和caddr
使我们的工作变得简单,并且很容易理解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
你有它。一厢情愿的想法和一些数据抽象救援。
数据抽象是一个非常强大的概念。您可能会想,&#34;为什么我们不在list
程序中使用count-pnz
而不是所有这些构造函数/选择器仪式?&#34; 答案可能不是很明显,但对于我来说这篇文章有点太多了。相反,我真诚地希望你查看我所链接的学习资源,因为我确信它们对你有很大帮助。
是的,这完全正确。让我们以不同的方式看待@DavinTryon说&#34; @ naomik的回答可以用一个列表以外的东西来定义(甚至只是函数)。&#34;
make-pnz
,positives
,negatives
和zero
。请注意,仍然必须履行合同,才能将此实施视为有效。
; 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-pnz
,positives
,negatives
和zeros
,但由于我们仍然履行了原始合同,我们{{1函数根本不需要改变。
答案 1 :(得分:0)
首先,我要说@ naomik的答案很棒。这是解构问题并逐步建立起来的方法。
当我第一次阅读这个问题时,我最终想到的是:
如何将整数列表减少到定义列表&#39;(p n z)?
所以reduce
表示可能使用foldl
或foldr
。
以下是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
。从本质上讲,这只会在列表的p
,n
或z
位置添加1(分别使用car
,cadr
和caddr
)
*请注意,此解决方案未进行优化。
答案 2 :(得分:0)
在计算过程中保持值的更好方法是创建一个包含您希望保留为参数的数据的帮助程序。更新值与通过提供新值进行递归相同:
(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的抽象,但帮助器中每次迭代的拆箱/装箱可能有点过分。虽然签订合同很好,但是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))
...)