; Copyright (c) 2011 Free Software Foundation, Inc.
; Copyright (c) 2005 Sebastian Egner and Jens Axel S{\o}gaard.
;
; Permission is hereby granted, free of charge, to any person obtaining
; a copy of this software and associated documentation files (the
; ``Software''), to deal in the Software without restriction, including
; without limitation the rights to use, copy, modify, merge, publish,
; distribute, sublicense, and/or sell copies of the Software, and to
; permit persons to whom the Software is furnished to do so, subject to
; the following conditions:
;
; The above copyright notice and this permission notice shall be
; included in all copies or substantial portions of the Software.
;
; THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND,
; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
; LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
; OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
; WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
;
; -----------------------------------------------------------------------
;
; Compare procedures SRFI (reference implementation)
; Sebastian.Egner@philips.com, Jensaxel@soegaard.net
; history of this file:
; SE, 14-Oct-2004: first version
; SE, 18-Oct-2004: 1st redesign: axioms for 'compare function'
; SE, 29-Oct-2004: 2nd redesign: higher order reverse/map/refine/unite
; SE, 2-Nov-2004: 3rd redesign: macros cond/refine-compare replace h.o.f's
; SE, 10-Nov-2004: (im,re) replaced by (re,im) in complex-compare
; SE, 11-Nov-2004: case-compare by case (not by cond); select-compare added
; SE, 12-Jan-2005: pair-compare-cdr
; SE, 15-Feb-2005: stricter typing for compare-<type>; pairwise-not=?
; SE, 16-Feb-2005: case-compare -> if-compare -> if3; <? </<? chain<? etc.
; JS, 24-Feb-2005: selection-compare added
; SE, 25-Feb-2005: selection-compare -> kth-largest modified; if<? etc.
; JS, 28-Feb-2005: kth-largest modified - is "stable" now
; SE, 28-Feb-2005: simplified pairwise-not=?/kth-largest; min/max debugged
; SE, 07-Apr-2005: compare-based type checks made explicit
; SE, 18-Apr-2005: added (rel? compare) and eq?-test
; SE, 16-May-2005: naming convention changed; compare-by< etc. optional x y
; =============================================================================
; Reference Implementation
; ========================
;
; in R5RS (including hygienic macros)
; + SRFI-16 (case-lambda)
; + SRFI-23 (error)
; + SRFI-27 (random-integer)
; Implementation remarks:
; * In general, the emphasis of this implementation is on correctness
; and portability, not on efficiency.
; * Variable arity procedures are expressed in terms of case-lambda
; in the hope that this will produce efficient code for the case
; where the arity is statically known at the call site.
; * In procedures that are required to type-check their arguments,
; we use (compare x x) for executing extra checks. This relies on
; the assumption that eq? is used to catch this case quickly.
; * Care has been taken to reference comparison procedures of R5RS
; only at the time the operations here are being defined. This
; makes it possible to redefine these operations, if need be.
; * For the sake of efficiency, some inlining has been done by hand.
; This is mainly expressed by macros producing defines.
; * Identifiers of the form compare:<something> are private.
;
; Hints for low-level implementation:
; * The basis of this SRFI are the atomic compare procedures,
; i.e. boolean-compare, char-compare, etc. and the conditionals
; if3, if=?, if<? etc., and default-compare. These should make
; optimal use of the available type information.
; * For the sake of speed, the reference implementation does not
; use a LET to save the comparison value c for the ERROR call.
; This can be fixed in a low-level implementation at no cost.
; * Type-checks based on (compare x x) are made explicit by the
; expression (compare:check result compare x ...).
; * Eq? should can used to speed up built-in compare procedures,
; but it can only be used after type-checking at least one of
; the arguments.
(define (compare:checked result compare . args)
(for-each (lambda (x) (compare x x)) args)
result)
; 3-sided conditional
(define-syntax-rule (if3 c less equal greater)
(case c
((-1) less)
(( 0) equal)
(( 1) greater)
(else (error "comparison value not in {-1,0,1}"))))
; 2-sided conditionals for comparisons
(define-syntax compare:if-rel?
(syntax-rules ()
((compare:if-rel? c-cases a-cases c consequence)
(compare:if-rel? c-cases a-cases c consequence (if #f #f)))
((compare:if-rel? c-cases a-cases c consequence alternate)
(case c
(c-cases consequence)
(a-cases alternate)
(else (error "comparison value not in {-1,0,1}"))))))
(define-syntax-rule (if=? arg ...)
(compare:if-rel? (0) (-1 1) arg ...))
(define-syntax-rule (if<? arg ...)
(compare:if-rel? (-1) (0 1) arg ...))
(define-syntax-rule (if>? arg ...)
(compare:if-rel? (1) (-1 0) arg ...))
(define-syntax-rule (if<=? arg ...)
(compare:if-rel? (-1 0) (1) arg ...))
(define-syntax-rule (if>=? arg ...)
(compare:if-rel? (0 1) (-1) arg ...))
(define-syntax-rule (if-not=? arg ...)
(compare:if-rel? (-1 1) (0) arg ...))
; predicates from compare procedures
(define-syntax-rule (compare:define-rel? rel? if-rel?)
(define rel?
(case-lambda
(() (lambda (x y) (if-rel? (default-compare x y) #t #f)))
((compare) (lambda (x y) (if-rel? (compare x y) #t #f)))
((x y) (if-rel? (default-compare x y) #t #f))
((compare x y)
(if (procedure? compare)
(if-rel? (compare x y) #t #f)
(error "not a procedure (Did you mean rel/rel??): " compare))))))
(compare:define-rel? =? if=?)
(compare:define-rel? <? if<?)
(compare:define-rel? >? if>?)
(compare:define-rel? <=? if<=?)
(compare:define-rel? >=? if>=?)
(compare:define-rel? not=? if-not=?)
; chains of length 3
(define-syntax-rule (compare:define-rel1/rel2? rel1/rel2? if-rel1? if-rel2?)
(define rel1/rel2?
(case-lambda
(()
(lambda (x y z)
(if-rel1? (default-compare x y)
(if-rel2? (default-compare y z) #t #f)
(compare:checked #f default-compare z))))
((compare)
(lambda (x y z)
(if-rel1? (compare x y)
(if-rel2? (compare y z) #t #f)
(compare:checked #f compare z))))
((x y z)
(if-rel1? (default-compare x y)
(if-rel2? (default-compare y z) #t #f)
(compare:checked #f default-compare z)))
((compare x y z)
(if-rel1? (compare x y)
(if-rel2? (compare y z) #t #f)
(compare:checked #f compare z))))))
(compare:define-rel1/rel2? </<? if<? if<?)
(compare:define-rel1/rel2? </<=? if<? if<=?)
(compare:define-rel1/rel2? <=/<? if<=? if<?)
(compare:define-rel1/rel2? <=/<=? if<=? if<=?)
(compare:define-rel1/rel2? >/>? if>? if>?)
(compare:define-rel1/rel2? >/>=? if>? if>=?)
(compare:define-rel1/rel2? >=/>? if>=? if>?)
(compare:define-rel1/rel2? >=/>=? if>=? if>=?)
; chains of arbitrary length
(define-syntax-rule (compare:define-chain-rel? chain-rel? if-rel?)
(define chain-rel?
(case-lambda
((compare)
#t)
((compare x1)
(compare:checked #t compare x1))
((compare x1 x2)
(if-rel? (compare x1 x2) #t #f))
((compare x1 x2 x3)
(if-rel? (compare x1 x2)
(if-rel? (compare x2 x3) #t #f)
(compare:checked #f compare x3)))
((compare x1 x2 . x3+)
(if-rel? (compare x1 x2)
(let chain? ((head x2) (tail x3+))
(if (null? tail)
#t
(if-rel? (compare head (car tail))
(chain? (car tail) (cdr tail))
(apply compare:checked #f
compare (cdr tail)))))
(apply compare:checked #f compare x3+))))))
(compare:define-chain-rel? chain=? if=?)
(compare:define-chain-rel? chain<? if<?)
(compare:define-chain-rel? chain>? if>?)
(compare:define-chain-rel? chain<=? if<=?)
(compare:define-chain-rel? chain>=? if>=?)
; pairwise inequality
(define pairwise-not=?
(let ((= =) (<= <=))
(case-lambda
((compare)
#t)
((compare x1)
(compare:checked #t compare x1))
((compare x1 x2)
(if-not=? (compare x1 x2) #t #f))
((compare x1 x2 x3)
(if-not=? (compare x1 x2)
(if-not=? (compare x2 x3)
(if-not=? (compare x1 x3) #t #f)
#f)
(compare:checked #f compare x3)))
((compare . x1+)
(let unequal? ((x x1+) (n (length x1+)) (unchecked? #t))
(if (< n 2)
(if (and unchecked? (= n 1))
(compare:checked #t compare (car x))
#t)
(let* ((i-pivot (random-integer n))
(x-pivot (list-ref x i-pivot)))
(let split ((i 0) (x x) (x< '()) (x> '()))
(if (null? x)
(and (unequal? x< (length x<) #f)
(unequal? x> (length x>) #f))
(if (= i i-pivot)
(split (+ i 1) (cdr x) x< x>)
(if3 (compare (car x) x-pivot)
(split (+ i 1) (cdr x) (cons (car x) x<) x>)
(if unchecked?
(apply compare:checked #f compare (cdr x))
#f)
(split (+ i 1) (cdr x) x< (cons (car x) x>)))))))))))))
; min/max
(define min-compare
(case-lambda
((compare x1)
(compare:checked x1 compare x1))
((compare x1 x2)
(if<=? (compare x1 x2) x1 x2))
((compare x1 x2 x3)
(if<=? (compare x1 x2)
(if<=? (compare x1 x3) x1 x3)
(if<=? (compare x2 x3) x2 x3)))
((compare x1 x2 x3 x4)
(if<=? (compare x1 x2)
(if<=? (compare x1 x3)
(if<=? (compare x1 x4) x1 x4)
(if<=? (compare x3 x4) x3 x4))
(if<=? (compare x2 x3)
(if<=? (compare x2 x4) x2 x4)
(if<=? (compare x3 x4) x3 x4))))
((compare x1 x2 . x3+)
(let min ((xmin (if<=? (compare x1 x2) x1 x2)) (xs x3+))
(if (null? xs)
xmin
(min (if<=? (compare xmin (car xs)) xmin (car xs))
(cdr xs)))))))
(define max-compare
(case-lambda
((compare x1)
(compare:checked x1 compare x1))
((compare x1 x2)
(if>=? (compare x1 x2) x1 x2))
((compare x1 x2 x3)
(if>=? (compare x1 x2)
(if>=? (compare x1 x3) x1 x3)
(if>=? (compare x2 x3) x2 x3)))
((compare x1 x2 x3 x4)
(if>=? (compare x1 x2)
(if>=? (compare x1 x3)
(if>=? (compare x1 x4) x1 x4)
(if>=? (compare x3 x4) x3 x4))
(if>=? (compare x2 x3)
(if>=? (compare x2 x4) x2 x4)
(if>=? (compare x3 x4) x3 x4))))
((compare x1 x2 . x3+)
(let max ((xmax (if>=? (compare x1 x2) x1 x2)) (xs x3+))
(if (null? xs)
xmax
(max (if>=? (compare xmax (car xs)) xmax (car xs))
(cdr xs)))))))
; kth-largest
(define kth-largest
(let ((= =) (< <))
(case-lambda
((compare k x0)
(case (modulo k 1)
((0) (compare:checked x0 compare x0))
(else (error "bad index" k))))
((compare k x0 x1)
(case (modulo k 2)
((0) (if<=? (compare x0 x1) x0 x1))
((1) (if<=? (compare x0 x1) x1 x0))
(else (error "bad index" k))))
((compare k x0 x1 x2)
(case (modulo k 3)
((0) (if<=? (compare x0 x1)
(if<=? (compare x0 x2) x0 x2)
(if<=? (compare x1 x2) x1 x2)))
((1) (if3 (compare x0 x1)
(if<=? (compare x1 x2)
x1
(if<=? (compare x0 x2) x2 x0))
(if<=? (compare x0 x2) x1 x0)
(if<=? (compare x0 x2)
x0
(if<=? (compare x1 x2) x2 x1))))
((2) (if<=? (compare x0 x1)
(if<=? (compare x1 x2) x2 x1)
(if<=? (compare x0 x2) x2 x0)))
(else (error "bad index" k))))
((compare k x0 . x1+) ; |x1+| >= 1
(if (not (and (integer? k) (exact? k)))
(error "bad index" k))
(let ((n (+ 1 (length x1+))))
(let kth ((k (modulo k n))
(n n) ; = |x|
(rev #t) ; are x<, x=, x> reversed?
(x (cons x0 x1+)))
(let ((pivot (list-ref x (random-integer n))))
(let split ((x x) (x< '()) (n< 0) (x= '()) (n= 0) (x> '()) (n> 0))
(if (null? x)
(cond
((< k n<)
(kth k n< (not rev) x<))
((< k (+ n< n=))
(if rev
(list-ref x= (- (- n= 1) (- k n<)))
(list-ref x= (- k n<))))
(else
(kth (- k (+ n< n=)) n> (not rev) x>)))
(if3 (compare (car x) pivot)
(split (cdr x) (cons (car x) x<) (+ n< 1) x= n= x> n>)
(split (cdr x) x< n< (cons (car x) x=) (+ n= 1) x> n>)
(split (cdr x) x< n< x= n= (cons (car x) x>) (+ n> 1))))))))))))
; compare functions from predicates
(define compare-by<
(case-lambda
((lt) (lambda (x y) (if (lt x y) -1 (if (lt y x) 1 0))))
((lt x y) (if (lt x y) -1 (if (lt y x) 1 0)))))
(define compare-by>
(case-lambda
((gt) (lambda (x y) (if (gt x y) 1 (if (gt y x) -1 0))))
((gt x y) (if (gt x y) 1 (if (gt y x) -1 0)))))
(define compare-by<=
(case-lambda
((le) (lambda (x y) (if (le x y) (if (le y x) 0 -1) 1)))
((le x y) (if (le x y) (if (le y x) 0 -1) 1))))
(define compare-by>=
(case-lambda
((ge) (lambda (x y) (if (ge x y) (if (ge y x) 0 1) -1)))
((ge x y) (if (ge x y) (if (ge y x) 0 1) -1))))
(define compare-by=/<
(case-lambda
((eq lt) (lambda (x y) (if (eq x y) 0 (if (lt x y) -1 1))))
((eq lt x y) (if (eq x y) 0 (if (lt x y) -1 1)))))
(define compare-by=/>
(case-lambda
((eq gt) (lambda (x y) (if (eq x y) 0 (if (gt x y) 1 -1))))
((eq gt x y) (if (eq x y) 0 (if (gt x y) 1 -1)))))
; refine and extend construction
(define-syntax refine-compare
(syntax-rules ()
((refine-compare)
0)
((refine-compare c1)
c1)
((refine-compare c1 c2 cs ...)
(if3 c1 -1 (refine-compare c2 cs ...) 1))))
(define-syntax select-compare
(syntax-rules (else)
((select-compare x y clause ...)
(let ((x-val x) (y-val y))
(select-compare (x-val y-val clause ...))))
; used internally: (select-compare (x y clause ...))
((select-compare (x y))
0)
((select-compare (x y (else c ...)))
(refine-compare c ...))
((select-compare (x y (t? c ...) clause ...))
(let ((t?-val t?))
(let ((tx (t?-val x)) (ty (t?-val y)))
(if tx
(if ty (refine-compare c ...) -1)
(if ty 1 (select-compare (x y clause ...)))))))))
(define-syntax cond-compare
(syntax-rules (else)
((cond-compare)
0)
((cond-compare (else cs ...))
(refine-compare cs ...))
((cond-compare ((tx ty) cs ...) clause ...)
(let ((tx-val tx) (ty-val ty))
(if tx-val
(if ty-val (refine-compare cs ...) -1)
(if ty-val 1 (cond-compare clause ...)))))))
; R5RS atomic types
(define-syntax compare:type-check
(syntax-rules ()
((compare:type-check type? type-name x)
(if (not (type? x))
(error (string-append "not " type-name ":") x)))
((compare:type-check type? type-name x y)
(begin (compare:type-check type? type-name x)
(compare:type-check type? type-name y)))))
(define-syntax-rule (compare:define-by=/< compare = < type? type-name)
(define compare
(let ((= =) (< <))
(lambda (x y)
(if (type? x)
(if (eq? x y)
0
(if (type? y)
(if (= x y) 0 (if (< x y) -1 1))
(error (string-append "not " type-name ":") y)))
(error (string-append "not " type-name ":") x))))))
(define (boolean-compare x y)
(compare:type-check boolean? "boolean" x y)
(if x (if y 0 1) (if y -1 0)))
(compare:define-by=/< char-compare char=? char<? char? "char")
(compare:define-by=/< char-compare-ci char-ci=? char-ci<? char? "char")
(compare:define-by=/< string-compare string=? string<? string? "string")
(compare:define-by=/< string-compare-ci string-ci=? string-ci<? string? "string")
(define (symbol-compare x y)
(compare:type-check symbol? "symbol" x y)
(string-compare (symbol->string x) (symbol->string y)))
(compare:define-by=/< integer-compare = < integer? "integer")
(compare:define-by=/< rational-compare = < rational? "rational")
(compare:define-by=/< real-compare = < real? "real")
(define (complex-compare x y)
(compare:type-check complex? "complex" x y)
(if (and (real? x) (real? y))
(real-compare x y)
(refine-compare (real-compare (real-part x) (real-part y))
(real-compare (imag-part x) (imag-part y)))))
(define (number-compare x y)
(compare:type-check number? "number" x y)
(complex-compare x y))
; R5RS compound data structures: dotted pair, list, vector
(define (pair-compare-car compare)
(lambda (x y)
(compare (car x) (car y))))
(define (pair-compare-cdr compare)
(lambda (x y)
(compare (cdr x) (cdr y))))
(define pair-compare
(case-lambda
; dotted pair
((pair-compare-car pair-compare-cdr x y)
(refine-compare (pair-compare-car (car x) (car y))
(pair-compare-cdr (cdr x) (cdr y))))
; possibly improper lists
((compare x y)
(cond-compare
(((null? x) (null? y)) 0)
(((pair? x) (pair? y)) (compare (car x) (car y))
(pair-compare compare (cdr x) (cdr y)))
(else (compare x y))))
; for convenience
((x y)
(pair-compare default-compare x y))))
(define list-compare
(case-lambda
((compare x y empty? head tail)
(cond-compare
(((empty? x) (empty? y)) 0)
(else (compare (head x) (head y))
(list-compare compare (tail x) (tail y) empty? head tail))))
; for convenience
(( x y empty? head tail)
(list-compare default-compare x y empty? head tail))
((compare x y )
(list-compare compare x y null? car cdr))
(( x y )
(list-compare default-compare x y null? car cdr))))
(define list-compare-as-vector
(case-lambda
((compare x y empty? head tail)
(refine-compare
(let compare-length ((x x) (y y))
(cond-compare
(((empty? x) (empty? y)) 0)
(else (compare-length (tail x) (tail y)))))
(list-compare compare x y empty? head tail)))
; for convenience
(( x y empty? head tail)
(list-compare-as-vector default-compare x y empty? head tail))
((compare x y )
(list-compare-as-vector compare x y null? car cdr))
(( x y )
(list-compare-as-vector default-compare x y null? car cdr))))
(define vector-compare
(let ((= =))
(case-lambda
((compare x y size ref)
(let ((n (size x)) (m (size y)))
(refine-compare
(integer-compare n m)
(let compare-rest ((i 0)) ; compare x[i..n-1] y[i..n-1]
(if (= i n)
0
(refine-compare (compare (ref x i) (ref y i))
(compare-rest (+ i 1))))))))
; for convenience
(( x y size ref)
(vector-compare default-compare x y size ref))
((compare x y )
(vector-compare compare x y vector-length vector-ref))
(( x y )
(vector-compare default-compare x y vector-length vector-ref)))))
(define vector-compare-as-list
(let ((= =))
(case-lambda
((compare x y size ref)
(let ((nx (size x)) (ny (size y)))
(let ((n (min nx ny)))
(let compare-rest ((i 0)) ; compare x[i..n-1] y[i..n-1]
(if (= i n)
(integer-compare nx ny)
(refine-compare (compare (ref x i) (ref y i))
(compare-rest (+ i 1))))))))
; for convenience
(( x y size ref)
(vector-compare-as-list default-compare x y size ref))
((compare x y )
(vector-compare-as-list compare x y vector-length vector-ref))
(( x y )
(vector-compare-as-list default-compare x y vector-length vector-ref)))))
; default compare
(define (default-compare x y)
(select-compare
x y
(null? 0)
(pair? (default-compare (car x) (car y))
(default-compare (cdr x) (cdr y)))
(boolean? (boolean-compare x y))
(char? (char-compare x y))
(string? (string-compare x y))
(symbol? (symbol-compare x y))
(number? (number-compare x y))
(vector? (vector-compare default-compare x y))
(else (error "unrecognized type in default-compare" x y))))
; Note that we pass default-compare to compare-{pair,vector} explictly.
; This makes sure recursion proceeds with this default-compare, which
; need not be the one in the lexical scope of compare-{pair,vector}.
; debug compare
(define (debug-compare c)
(define (checked-value c x y)
(let ((c-xy (c x y)))
(if (or (eqv? c-xy -1) (eqv? c-xy 0) (eqv? c-xy 1))
c-xy
(error "compare value not in {-1,0,1}" c-xy (list c x y)))))
(define (random-boolean)
(zero? (random-integer 2)))
(define q ; (u v w) such that u <= v, v <= w, and not u <= w
'#(
;x < y x = y x > y [x < z]
0 0 0 ; y < z
0 (z y x) (z y x) ; y = z
0 (z y x) (z y x) ; y > z
;x < y x = y x > y [x = z]
(y z x) (z x y) 0 ; y < z
(y z x) 0 (x z y) ; y = z
0 (y x z) (x z y) ; y > z
;x < y x = y x > y [x > z]
(x y z) (x y z) 0 ; y < z
(x y z) (x y z) 0 ; y = z
0 0 0 ; y > z
))
(let ((z? #f) (z #f)) ; stored element from previous call
(lambda (x y)
(let ((c-xx (checked-value c x x))
(c-yy (checked-value c y y))
(c-xy (checked-value c x y))
(c-yx (checked-value c y x)))
(if (not (zero? c-xx))
(error "compare error: not reflexive" c x))
(if (not (zero? c-yy))
(error "compare error: not reflexive" c y))
(if (not (zero? (+ c-xy c-yx)))
(error "compare error: not anti-symmetric" c x y))
(if z?
(let ((c-xz (checked-value c x z))
(c-zx (checked-value c z x))
(c-yz (checked-value c y z))
(c-zy (checked-value c z y)))
(if (not (zero? (+ c-xz c-zx)))
(error "compare error: not anti-symmetric" c x z))
(if (not (zero? (+ c-yz c-zy)))
(error "compare error: not anti-symmetric" c y z))
(let ((ijk (vector-ref q (+ c-xy (* 3 c-yz) (* 9 c-xz) 13))))
(if (list? ijk)
(apply error
"compare error: not transitive"
c
(map (lambda (i) (case i ((x) x) ((y) y) ((z) z)))
ijk)))))
(set! z? #t))
(set! z (if (random-boolean) x y)) ; randomized testing
c-xy))))