Functional variant of 'oneof' function in Racket

孤街浪徒 提交于 2019-11-28 05:42:51

问题


I have written following function to find if one and only one of 5 variables is true:

(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 takes only 2 arguments)

However, it is very imperative and not functional. Moreover, I want to write a function (oneof N) which will be generic rather than specific for 5 variables. How can this be done? Thanks.

Edit: As pointed out in the comments, the code is 'repetitive' and not 'imperative', though the need for its improvement remains.


回答1:


Your final note is accurate: it sounds like xor is the right function but this, but it only takes two arguments. It would likely be better if xor took any number of arguments, but given that it doesn’t, we can implement it ourselves.

Perhaps the most naïve way would just to be to count the number of truthy values and check if that number is precisely 1. We can do this with count or for/sum, depending on your preference:

; 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))))

Both of these work, but they don’t preserve a useful property of Racket’s xor, which returns the single truthy element upon success rather than always returning a boolean. To do this, we could use a fold, using foldl, foldr, or for/fold. However, given that we want to ideally exit as soon as possible, using the #:final option of for/fold is pretty convenient:

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

However, this is actually still not really optimal. The two-argument version of xor is a function, not a macro like and and or, because it cannot be lazy in either of its arguments. However, a many-argument xor actually can be. In order to add this short-circuiting behavior, we can write xor as a macro:

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

In general, this works just like the function versions of xor:

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

However, like and and or, it sometimes “short-circuits”, not evaluating expressions if their results will not mater:

> (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

(Note that hello! is never printed in the last example.)

Is this a good idea, is it a bad idea…? I don’t really know. It does seem unlikely that this behavior will ever be super useful, and it adds a lot of complexity. It also prevents xor from being used higher-order, but you could get around that with syntax-id-rules and expanding to the procedure version when xor is used in an expression position. Still, it’s potentially interesting, and it makes its behavior more consistent with and and or, so I figured I’d include it for completeness.




回答2:


You can literally count how many true values you have in a list of arbitrary length, if that number is 1 then we're good (remember that in Scheme any non-false value is considered true). Also notice how to create a procedure with a variable number of arguments, using the dot notation:

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

For example:

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



回答3:


Another solution, more concise, is the following:

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


来源:https://stackoverflow.com/questions/38820738/functional-variant-of-oneof-function-in-racket

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!