Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
334 views
in Technique[技术] by (71.8m points)

scheme - Functional variant of 'oneof' function in Racket

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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...