【问题标题】:Functional variant of 'oneof' function in RacketRacket 中“oneof”函数的功能变体
【发布时间】:2016-12-13 17:29:32
【问题描述】:

我编写了以下函数来查找 5 个变量中是否只有一个为真:

(define (oneof v w x y z)
  (or (and v (not w) (not x) (not y)  (not z))
      (and w (not v) (not x) (not y)  (not z))
      (and x (not v) (not w) (not y)  (not z))
      (and y (not v) (not w) (not x)  (not z))
      (and z (not v) (not w) (not x)  (not y))  ))

(xor 只接受 2 个参数)

但是,它是非常必要的,并且没有功能。此外,我想编写一个函数(oneof N),它是通用的,而不是特定于 5 个变量。如何才能做到这一点?谢谢。

编辑:正如 cmets 中所指出的,代码是“重复的”而不是“必要的”,尽管仍然需要对其进行改进。

【问题讨论】:

  • 对于它的价值,你发布的代码没有什么是非常必要的(没有副作用,一切都是引用透明的),它只是非常重复.
  • 我想要一个循环而不是重复。也使它更通用,而不是仅用于 5 个变量。
  • 是的,我同意对您发布的代码进行了改进 — 也应避免不必要的重复代码 — 但我只是指出您认为您的代码是命令式的观察并不真正准确. Imperative 不是 bad 的同义词,就像函数式程序员有时会让你相信的那样。 ;)
  • 我在上面的问题的编辑中添加了这个观察结果。

标签: functional-programming scheme racket xor


【解决方案1】:

您的最后一点是准确的:听起来xor 是正确的函数,但它只需要两个参数。如果xor 接受任意数量的参数可能会更好,但鉴于它没有,我们可以自己实现它。

也许最天真的方法就是计算真值的数量并检查该数字是否正好是 1。我们可以使用 countfor/sum 来执行此操作,具体取决于您的偏好:

; using count
(define (xor . args)
  (= 1 (count identity args)))

; using for/sum
(define (xor . args)
  (= 1 (for/sum ([x (in-list args)])
         (if x 1 0))))

这两种方法都有效,但它们没有保留 Racket 的 xor 的有用属性,它在成功时返回单个真值元素,而不是总是返回布尔值。为此,我们可以使用折叠,使用foldlfoldrfor/fold。但是,考虑到我们希望尽快退出,使用for/fold#:final 选项非常方便:

(define (xor . args)
  (for/fold ([found #f])
            ([arg (in-list args)])
            #:final (and found arg)
    (if (and found arg) #f
        (or found arg))))

然而,这实际上仍然不是真正的最佳xor 的双参数版本是一个函数,而不是像 andor 这样的宏,因为它的任何一个参数都不能是惰性的。但是,多参数xor 实际上可以。为了添加这种短路行为,我们可以将xor 写成一个宏:

(define-syntax xor
  (syntax-rules ()
    [(_) #f]
    [(_ x) x]
    [(_ x rest ...)
     (let ([v x])
       (if v
           (and (nor rest ...) v)
           (xor rest ...)))]))

一般来说,这就像xor的函数版本一样工作:

> (xor #f #f #f #f #f)
#f
> (xor #f #f 1 #f #f)
1
> (xor #f #f 1 2 #f)
#f

但是,像 andor 一样,它有时会“短路”,如果表达式的结果不匹配,则不会评估表达式:

> (xor #f #f #f #f (begin (displayln "hello!") #f))
hello!
#f
> (xor #f #f 1 #f (begin (displayln "hello!") #f))
hello!
1
> (xor #f #f 1 2 (begin (displayln "hello!") #f))
#f

(请注意,hello! 在最后一个示例中从未打印出来。)

这是个好主意,还是个坏主意……?我真的不知道。这种行为似乎不太可能超级有用,而且它增加了很多复杂性。它还可以防止xor 被高阶使用,但是当xor 用于表达式位置时,您可以使用syntax-id-rules 解决这个问题并扩展到过程版本。不过,它可能很有趣,而且它的行为与andor 更加一致,所以我想为了完整起见我会包含它。

【讨论】:

【解决方案2】:

您可以逐字地计算在任意长度的列表中有多少 true 值,如果该数字是 1,那么我们很好(请记住,在 Scheme 中任何非 @ 987654323@ 值被认为是true)。另请注意如何使用点符号创建具有可变数量参数的过程:

(define (oneof . vars)
  (= 1
     (count (lambda (v) (not (false? v)))
            vars)))

例如:

(oneof #f #f #f #f #t)
=> #t
(oneof #f #f #f #f #f)
=> #f
(oneof #t #t #t #t #t)
=> #f

【讨论】:

    【解决方案3】:

    另一种更简洁的解决方案如下:

    (define (oneof . vars)
      (= 1 (count identity vars)))
    

    【讨论】:

    • @rnso identity 函数只是一个非常简单的函数,它总是返回它的参数。在这种情况下,它被用作count 的某种谓词,当谓词应用于它们时,它会计算所有导致“真实”结果的参数。在Scheme中,所有非#f的值都被认为是真值,所以(count identity lst)只计算lst中有多少元素是非#f
    • 实际上是 (count (lambda (x) x) vars)。
    猜你喜欢
    • 2014-03-08
    • 2017-06-03
    • 2020-05-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-01
    • 2020-04-09
    相关资源
    最近更新 更多