查找输入中出现在某个标记值(例如 -999)之前的非负数的平均值
Find the average of non-negative numbers that occur in the input before some sentinel value (such as -999)
遇到论文中提到一个实验的编程任务https://cs.brown.edu/~sk/Publications/Papers/Published/kf-prog-paradigms-and-beyond/paper.pdf
任务:“粗略地说,Rainfall 要求计算非负数的平均值
出现在某些标记值(例如 -999)之前的输入中
我决定尝试使用高级学生语言 (ASL) 以 HtDP 方式解决它。
找到工作解决方案后,我想知道是否有办法让它更优雅。
此外,还不清楚如何根据 HtDP 书中提供的设计方法模板化这样的函数 https://htdp.org/
这是我的解决方案:
;; ListOfNumber Integer -> Integer
;; produce the average of numbers in the list before the guard number
;; INVARIANTS:
;; - lon starts with at least one non-negative integer
;; - guard number is a member of lon
(check-expect (list-average (cons 25 (cons -999 empty)) -999) (/ 25 1))
(check-expect (list-average (cons 8 (cons 5 (cons 17 (cons -999 (cons 12 (cons 25 empty)))))) -999) (/ (+ 8 5 17) 3))
;(define (list-average lon n) 0) ;stub
(define (list-average lon n)
(local [(define (numbers-before-n lon n)
(cond [(or (empty? lon) (= (first lon) n)) empty]
[else (cons (first lon)
(numbers-before-n (rest lon) n))]))
(define VALID-NUMBERS (numbers-before-n lon n))
(define SUM (foldr + 0 VALID-NUMBERS))
(define AVERAGE (/ SUM (length VALID-NUMBERS)))]
AVERAGE))
我喜欢你的解决方案!但是,我认为通过使用 #lang racket
您可以利用更多内置程序来编写更短且更地道的答案:
(define (list-average lon n)
(let ([valid (takef lon (negate (curry = n)))])
(/ (apply + valid) (length valid))))
解释:
takef
将获取列表的所有元素,直到满足谓词指定的条件。在这种情况下,谓词是 (lambda (x) (not (= n x)))
,但即使这样也可以简化 - 继续阅读
curry
将采用一个函数和一个参数并生成一个具有该参数集的新函数,但仍希望使用另一个参数调用
negate
将 return 谓词的否定
apply +
是对列表求和的更简单方法
我将尝试解决您问题的 HtDP 部分,主要基于本书的 II - 任意大数据 部分。有关于如何构建此类程序设计的指南,包括函数模板。
首先,请注意您的代码:
- 函数签名:(i) 涉及整数,不能假定为标记值,而且大多数情况下,
AVERAGE
不假定 (ii) 涉及 ListOfNumber,不包括其数据定义。 数据定义将决定函数模板。
- 关于您的不变量(我称它们为 "assumptions",因为没有规定以某种方式在代码中维护它们):它们真的有必要吗?
guard
是否必须是 lon
的成员? lon
是否必须开始甚至至少有一个非负元素? lon
必须是非空的吗?这些答案也会影响数据定义。
考虑:
;; ListOfNumber is one of: VS ;; ListOfNumber is one of:
;; - empty ;; - (cons Number empty)
;; - (cons Number ListOfNumber) ;; - (cons Number ListOfNumber)
右边的数据定义意味着可以 return empty
的函数,例如您的本地 numbers-before-n
,不能声明它 returns ListOfNumber。
所以:
;; Advanced Student Language
;; Data Definitions
;; ListOfNumber is one of:
;; - empty
;; - (cons Number ListOfNumber)
;; interpret. a list of numbers as the input to Rainfall
;; Template for a function consuming ListOfNumber
(define (fun-for-lon lon)
(cond [(empty? lon) (...)]
[else
(... (first lon) ...
... (fun-for-lon (rest lon)) ...)]))
;; Template rules used:
;; - one of: 2 cases
;; - atomic distinct: empty
;; - compound: (cons Number ListOfNumber)
;; - self-reference: (rest lon) is ListOfNumber
;; If the guard value was defined as a constant, the template would be the one above.
;; but i'll follow your way of it being a parameter.
;; the guard as input is a Number, thus Atomic, so the template becomes
(define (fun-for-lon-n lon n)
(cond [(empty? lon) (... n)]
[else
(... n (first lon) ...
... (fun-for-lon-n (rest lon) n) ...)]))
;; We could just return zero also if:
;; input is empty or without non-negative values before guard
;; But IF we decide to distinguish what Rainfall produces depending on input cases:
;; RainfallResult is one of:
;; - "empty input"
;; - "no values"
;; - Number[0, +inf.0)
;; interpret.
;; - "empty input" means the input had no values
;; - "no values" means input had no non-negative values before guard value
;; - Number[0, +inf.0) means the input had valid values before guard and their average
;; for completeness, template for function consuming RainfallResult has as template:
(define (fun-for-rr rr)
(cond [(and (string? rr) (string=? rr "empty input")) (...)]
[(and (string? rr) (string=? rr "no values")) (...)]
[(and (number? rr) (>= rr 0)) (... rr)]))
;; Template rules used:
;; - one of: 3 cases
;; - atomic distinct: "empty input"
;; - atomic distinct: "no values"
;; - atomic non-distinct: Number[0, +inf.0)
;; Functions
;; rainfall (the list-average of your code)
;; ListOfNumber Number -> RainfallResult
;; produce the average of non-negative numbers that occur in the input before guard value
(check-expect (rainfall empty -99) "empty input") ;lon is empty
(check-expect (rainfall (list -99) -99) "no values") ;lon only includes guard
(check-expect (rainfall (list -99 1 2.5 3) -99) "no values") ;lon starts with guard
(check-expect (rainfall (list -1 -2.5 -99 2) -99) "no values") ;only neg. nums,yes guard
(check-expect (rainfall (list -1.3 -2.9 -0.2) -99) "no values") ;only neg. nums, no guard
(check-expect (rainfall (list 1.5 -0.9 2.5) -99) (/ (+ 1.5 2.5) 2)) ;no guard
(check-expect (rainfall (list 0 0 0 0 -99 3) -99) (/ (+ 0 0 0 0) 4)) ;guard neg
(check-expect (rainfall (list 1 -1 0.1 2.5 99 3) 99) (/ (+ 1 0.1 2.5) 3)) ;guard pos
(check-expect (rainfall (list 1 -1 0.1 2.5 99.5 3) 99.5) (/ (+ 1 0.1 2.5) 3)) ;guard float
;(define (rainfall lon guard) 0) ;stub
;; When we realize that rainfall is composed of subtasks,
;; we can write the composition and add the compositing functions to WishList,
;; (and consider if the language provides such a function)
;; used template from ListOfNumber with n
(define (rainfall lon guard)
(cond [(empty? lon) "empty input"]
[else
(average-lon (nn-nums-before-guard lon guard))]))
;; Wish List
;; average-lon (done)
;; ListOfNumber -> RainfallResult
;; produce the average of lon; if lon is empty, return "no values"
;; Assume that lon is a list of non-negative numbers
(check-expect (average-lon empty) "no values")
(check-expect (average-lon (list 0)) (/ 0 1))
(check-expect (average-lon (list 1 3 5 0)) (/ (+ 1 3 5 0) 4))
;(define (average-lon lon) 0) ;stub
;; used template from ListOfNumber
#;
(define (average-lon lon)
(cond [(empty? lon) "no values"]
[else
(/ (sum lon) (how-many lon))]))
;; to save some space:
;; the (how-many lon), well, is (length lon)
(define (average-lon lon)
(cond [(empty? lon) "no values"]
[else
(/ (sum lon) (length lon))]))
;; for the (sum lon) we could also:
;; either (foldr + 0 lon) like in your code
;; or (apply + lon) like in the accepted answer
;; but for now let's add it to the wish list and implement it
;; nn-nums-before-guard (done)
;; ListOfNumber Number -> ListOfNumber
;; produce a list of non-negative nums occuring in lon before n
(check-expect (nn-nums-before-guard empty -99) empty)
(check-expect (nn-nums-before-guard (list -99 1 2) -99) empty)
(check-expect (nn-nums-before-guard (list -1 -2) -99) empty)
(check-expect (nn-nums-before-guard (list 1 2 3) -99) (list 1 2 3))
(check-expect (nn-nums-before-guard (list 1 2 3 -99 1) -99) (list 1 2 3))
(check-expect (nn-nums-before-guard (list 1 -2 3 -99 1) -99) (list 1 3))
;(define (nn-nums-before-guard lon n) empty) ;stub
;; used template from ListOfNumber with n
(define (nn-nums-before-guard lon guard)
(cond [(empty? lon) empty]
[else
(cond
[(= (first lon) guard) empty]
[(< (first lon) 0) (nn-nums-before-guard (rest lon) guard)]
[else (cons (first lon)
(nn-nums-before-guard (rest lon) guard))])]))
;; sum (done)
;; ListOfNumber -> Number
;; produce the sum of numbers in lon
(check-expect (sum empty) 0)
(check-expect (sum (list 1)) 1)
(check-expect (sum (list 1 -1 2 -2 3 -3)) 0)
;(define (sum lon) 0) ;stub
;; used template from ListOfNumber
(define (sum lon)
(cond [(empty? lon) 0]
[else
(+ (first lon)
(sum (rest lon)))]))
遇到论文中提到一个实验的编程任务https://cs.brown.edu/~sk/Publications/Papers/Published/kf-prog-paradigms-and-beyond/paper.pdf
任务:“粗略地说,Rainfall 要求计算非负数的平均值 出现在某些标记值(例如 -999)之前的输入中
我决定尝试使用高级学生语言 (ASL) 以 HtDP 方式解决它。
找到工作解决方案后,我想知道是否有办法让它更优雅。 此外,还不清楚如何根据 HtDP 书中提供的设计方法模板化这样的函数 https://htdp.org/
这是我的解决方案:
;; ListOfNumber Integer -> Integer
;; produce the average of numbers in the list before the guard number
;; INVARIANTS:
;; - lon starts with at least one non-negative integer
;; - guard number is a member of lon
(check-expect (list-average (cons 25 (cons -999 empty)) -999) (/ 25 1))
(check-expect (list-average (cons 8 (cons 5 (cons 17 (cons -999 (cons 12 (cons 25 empty)))))) -999) (/ (+ 8 5 17) 3))
;(define (list-average lon n) 0) ;stub
(define (list-average lon n)
(local [(define (numbers-before-n lon n)
(cond [(or (empty? lon) (= (first lon) n)) empty]
[else (cons (first lon)
(numbers-before-n (rest lon) n))]))
(define VALID-NUMBERS (numbers-before-n lon n))
(define SUM (foldr + 0 VALID-NUMBERS))
(define AVERAGE (/ SUM (length VALID-NUMBERS)))]
AVERAGE))
我喜欢你的解决方案!但是,我认为通过使用 #lang racket
您可以利用更多内置程序来编写更短且更地道的答案:
(define (list-average lon n)
(let ([valid (takef lon (negate (curry = n)))])
(/ (apply + valid) (length valid))))
解释:
takef
将获取列表的所有元素,直到满足谓词指定的条件。在这种情况下,谓词是(lambda (x) (not (= n x)))
,但即使这样也可以简化 - 继续阅读curry
将采用一个函数和一个参数并生成一个具有该参数集的新函数,但仍希望使用另一个参数调用negate
将 return 谓词的否定apply +
是对列表求和的更简单方法
我将尝试解决您问题的 HtDP 部分,主要基于本书的 II - 任意大数据 部分。有关于如何构建此类程序设计的指南,包括函数模板。
首先,请注意您的代码:
- 函数签名:(i) 涉及整数,不能假定为标记值,而且大多数情况下,
AVERAGE
不假定 (ii) 涉及 ListOfNumber,不包括其数据定义。 数据定义将决定函数模板。 - 关于您的不变量(我称它们为 "assumptions",因为没有规定以某种方式在代码中维护它们):它们真的有必要吗?
guard
是否必须是lon
的成员?lon
是否必须开始甚至至少有一个非负元素?lon
必须是非空的吗?这些答案也会影响数据定义。
考虑:
;; ListOfNumber is one of: VS ;; ListOfNumber is one of:
;; - empty ;; - (cons Number empty)
;; - (cons Number ListOfNumber) ;; - (cons Number ListOfNumber)
右边的数据定义意味着可以 return empty
的函数,例如您的本地 numbers-before-n
,不能声明它 returns ListOfNumber。
所以:
;; Advanced Student Language
;; Data Definitions
;; ListOfNumber is one of:
;; - empty
;; - (cons Number ListOfNumber)
;; interpret. a list of numbers as the input to Rainfall
;; Template for a function consuming ListOfNumber
(define (fun-for-lon lon)
(cond [(empty? lon) (...)]
[else
(... (first lon) ...
... (fun-for-lon (rest lon)) ...)]))
;; Template rules used:
;; - one of: 2 cases
;; - atomic distinct: empty
;; - compound: (cons Number ListOfNumber)
;; - self-reference: (rest lon) is ListOfNumber
;; If the guard value was defined as a constant, the template would be the one above.
;; but i'll follow your way of it being a parameter.
;; the guard as input is a Number, thus Atomic, so the template becomes
(define (fun-for-lon-n lon n)
(cond [(empty? lon) (... n)]
[else
(... n (first lon) ...
... (fun-for-lon-n (rest lon) n) ...)]))
;; We could just return zero also if:
;; input is empty or without non-negative values before guard
;; But IF we decide to distinguish what Rainfall produces depending on input cases:
;; RainfallResult is one of:
;; - "empty input"
;; - "no values"
;; - Number[0, +inf.0)
;; interpret.
;; - "empty input" means the input had no values
;; - "no values" means input had no non-negative values before guard value
;; - Number[0, +inf.0) means the input had valid values before guard and their average
;; for completeness, template for function consuming RainfallResult has as template:
(define (fun-for-rr rr)
(cond [(and (string? rr) (string=? rr "empty input")) (...)]
[(and (string? rr) (string=? rr "no values")) (...)]
[(and (number? rr) (>= rr 0)) (... rr)]))
;; Template rules used:
;; - one of: 3 cases
;; - atomic distinct: "empty input"
;; - atomic distinct: "no values"
;; - atomic non-distinct: Number[0, +inf.0)
;; Functions
;; rainfall (the list-average of your code)
;; ListOfNumber Number -> RainfallResult
;; produce the average of non-negative numbers that occur in the input before guard value
(check-expect (rainfall empty -99) "empty input") ;lon is empty
(check-expect (rainfall (list -99) -99) "no values") ;lon only includes guard
(check-expect (rainfall (list -99 1 2.5 3) -99) "no values") ;lon starts with guard
(check-expect (rainfall (list -1 -2.5 -99 2) -99) "no values") ;only neg. nums,yes guard
(check-expect (rainfall (list -1.3 -2.9 -0.2) -99) "no values") ;only neg. nums, no guard
(check-expect (rainfall (list 1.5 -0.9 2.5) -99) (/ (+ 1.5 2.5) 2)) ;no guard
(check-expect (rainfall (list 0 0 0 0 -99 3) -99) (/ (+ 0 0 0 0) 4)) ;guard neg
(check-expect (rainfall (list 1 -1 0.1 2.5 99 3) 99) (/ (+ 1 0.1 2.5) 3)) ;guard pos
(check-expect (rainfall (list 1 -1 0.1 2.5 99.5 3) 99.5) (/ (+ 1 0.1 2.5) 3)) ;guard float
;(define (rainfall lon guard) 0) ;stub
;; When we realize that rainfall is composed of subtasks,
;; we can write the composition and add the compositing functions to WishList,
;; (and consider if the language provides such a function)
;; used template from ListOfNumber with n
(define (rainfall lon guard)
(cond [(empty? lon) "empty input"]
[else
(average-lon (nn-nums-before-guard lon guard))]))
;; Wish List
;; average-lon (done)
;; ListOfNumber -> RainfallResult
;; produce the average of lon; if lon is empty, return "no values"
;; Assume that lon is a list of non-negative numbers
(check-expect (average-lon empty) "no values")
(check-expect (average-lon (list 0)) (/ 0 1))
(check-expect (average-lon (list 1 3 5 0)) (/ (+ 1 3 5 0) 4))
;(define (average-lon lon) 0) ;stub
;; used template from ListOfNumber
#;
(define (average-lon lon)
(cond [(empty? lon) "no values"]
[else
(/ (sum lon) (how-many lon))]))
;; to save some space:
;; the (how-many lon), well, is (length lon)
(define (average-lon lon)
(cond [(empty? lon) "no values"]
[else
(/ (sum lon) (length lon))]))
;; for the (sum lon) we could also:
;; either (foldr + 0 lon) like in your code
;; or (apply + lon) like in the accepted answer
;; but for now let's add it to the wish list and implement it
;; nn-nums-before-guard (done)
;; ListOfNumber Number -> ListOfNumber
;; produce a list of non-negative nums occuring in lon before n
(check-expect (nn-nums-before-guard empty -99) empty)
(check-expect (nn-nums-before-guard (list -99 1 2) -99) empty)
(check-expect (nn-nums-before-guard (list -1 -2) -99) empty)
(check-expect (nn-nums-before-guard (list 1 2 3) -99) (list 1 2 3))
(check-expect (nn-nums-before-guard (list 1 2 3 -99 1) -99) (list 1 2 3))
(check-expect (nn-nums-before-guard (list 1 -2 3 -99 1) -99) (list 1 3))
;(define (nn-nums-before-guard lon n) empty) ;stub
;; used template from ListOfNumber with n
(define (nn-nums-before-guard lon guard)
(cond [(empty? lon) empty]
[else
(cond
[(= (first lon) guard) empty]
[(< (first lon) 0) (nn-nums-before-guard (rest lon) guard)]
[else (cons (first lon)
(nn-nums-before-guard (rest lon) guard))])]))
;; sum (done)
;; ListOfNumber -> Number
;; produce the sum of numbers in lon
(check-expect (sum empty) 0)
(check-expect (sum (list 1)) 1)
(check-expect (sum (list 1 -1 2 -2 3 -3)) 0)
;(define (sum lon) 0) ;stub
;; used template from ListOfNumber
(define (sum lon)
(cond [(empty? lon) 0]
[else
(+ (first lon)
(sum (rest lon)))]))