查找输入中出现在某个标记值(例如 -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)))]))