Skip to content

Commit a406c24

Browse files
committed
Add lexicographic-comparator
1 parent 9610fbf commit a406c24

File tree

2 files changed

+85
-7
lines changed

2 files changed

+85
-7
lines changed

base/comparator.rkt

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
[comparator-of-constants (-> any/c ... comparator?)]
1919
[comparator-min (-> comparator? any/c any/c ... any/c)]
2020
[comparator-max (-> comparator? any/c any/c ... any/c)]
21+
[lexicographic-comparator (-> comparator? (comparator/c (sequence/c any/c)))]
2122
[comparison? (-> any/c boolean?)]
2223
[lesser comparison?]
2324
[greater comparison?]
@@ -45,7 +46,9 @@
4546
guard
4647
racket/contract/combinator
4748
racket/list
49+
racket/match
4850
racket/math
51+
racket/sequence
4952
racket/set
5053
rebellion/base/immutable-string
5154
rebellion/base/symbol
@@ -152,6 +155,25 @@
152155
#:name enclosing-function-name))
153156

154157

158+
(define/name (lexicographic-comparator element-comparator)
159+
(make-comparator
160+
(λ (xs ys)
161+
(define-values (more-xs? get-next-x) (sequence-generate xs))
162+
(define-values (more-ys? get-next-y) (sequence-generate ys))
163+
(let loop ()
164+
(match (list (more-xs?) (more-ys?))
165+
[(list #false #false) equivalent]
166+
[(list #false #true) lesser]
167+
[(list #true #false) greater]
168+
[(list #true #true)
169+
(define x (get-next-x))
170+
(define y (get-next-y))
171+
(match (compare element-comparator x y)
172+
[(== equivalent) (loop)]
173+
[result result])])))
174+
#:name enclosing-function-name))
175+
176+
155177
(define (comparator-min comparator v . vs)
156178
(for/fold ([min-so-far v])
157179
([v (in-list vs)]
@@ -322,6 +344,31 @@
322344
#rx"'\\(small medium small\\)" (λ () (comparator-of-constants 'small 'medium 'small)))
323345
(check-exn exn:fail:contract? (λ () (comparator-of-constants #false #false))))
324346

347+
(test-case (name-string lexicographic-comparator)
348+
(define nums<=> (lexicographic-comparator real<=>))
349+
(check-equal? (compare nums<=> (list 1) (list 1)) equivalent)
350+
(check-equal? (compare nums<=> (list 2) (list 1)) greater)
351+
(check-equal? (compare nums<=> (list 1) (list 2)) lesser)
352+
(check-equal? (compare nums<=> (list 1 2) (list 1 2)) equivalent)
353+
(check-equal? (compare nums<=> (list 1 3) (list 1 2)) greater)
354+
(check-equal? (compare nums<=> (list 1 2) (list 1 3)) lesser)
355+
(check-equal? (compare nums<=> (list 1) (list 1 2)) lesser)
356+
(check-equal? (compare nums<=> (list 1 2) (list 1)) greater)
357+
(check-equal? (compare nums<=> (list 1 2 3) (list 1 2)) greater)
358+
(check-equal? (compare nums<=> (list 1 2) (list 1 2 3)) lesser)
359+
(check-equal? (compare nums<=> (list 2) (list 1 2)) greater)
360+
(check-equal? (compare nums<=> (list 1 2) (list 2)) lesser)
361+
(check-equal? (compare nums<=> (list 0) (list 1 2)) lesser)
362+
(check-equal? (compare nums<=> (list 1 2) (list 0)) greater)
363+
(check-equal? (compare nums<=> (list 1 2) (list 0)) greater)
364+
(check-equal? (compare nums<=> (list 1 2 3 4 5) (vector 1 2 3 4 5)) equivalent)
365+
(check-equal? (compare nums<=> (vector 1 2 3 4 5) (list 1 2 3 4 5)) equivalent)
366+
(check-equal? (compare nums<=> (list 1 2 3 4 5) (vector-immutable 1 2 3 4 5)) equivalent)
367+
(check-equal? (compare nums<=> (vector-immutable 1 2 3 4 5) (list 1 2 3 4 5)) equivalent)
368+
(check-equal? (compare nums<=> (vector 1 2 3 4 5) (vector-immutable 1 2 3 4 5)) equivalent)
369+
(check-equal? (compare nums<=> (vector-immutable 1 2 3 4 5) (vector 1 2 3 4 5)) equivalent)
370+
(check-equal? (compare (lexicographic-comparator char<=>) (list #\a #\b #\c) "abc") equivalent))
371+
325372
(test-case (name-string comparator-min)
326373
(check-equal? (comparator-min real<=> 1 99 99) 1)
327374
(check-equal? (comparator-min real<=> 99 1 99) 1)

base/comparator.scrbl

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@
3232
'rebellion/type/tuple)
3333
#:private (list 'racket/base)))
3434

35+
@(define lexicographic-order-url "https://en.wikipedia.org/wiki/Lexicographic_order")
36+
37+
3538
@title{Comparators}
3639
@defmodule[rebellion/base/comparator]
3740

@@ -199,6 +202,34 @@ with equality unless otherwise stated.
199202
(sorting (comparator-chain gemstone-by-type<=> gemstone-by-weight<=>))
200203
#:into into-list))}
201204

205+
206+
@defproc[(lexicographic-comparator [element-comparator comparator?])
207+
(comparator/c (sequence/c any/c))]{
208+
Constructs a @tech{comparator} of
209+
@tech[#:doc '(lib "scribblings/reference/reference.scrbl")]{sequences} which compares sequences in
210+
@hyperlink[lexicographic-order-url]{lexicographic order} by comparing each sequence element with
211+
@racket[element-comparator].
212+
213+
@(examples
214+
#:eval (make-evaluator) #:once
215+
(eval:no-prompt
216+
(define real-seq<=> (lexicographic-comparator real<=>)))
217+
(compare real-seq<=> (list 1 2 3) (list 3 2 1))
218+
(compare real-seq<=> (list 1 2 3) (list 1))
219+
(compare real-seq<=> (list 1 2 3) (list 2))
220+
(compare real-seq<=> (list 1 2 3) (list 1 2 3))
221+
(compare real-seq<=> (list 1 2 3) (list 1 2 3 0)))
222+
223+
The two sequences need not be of the same type: they will be considered equivalent if they have
224+
equivalent elements in the same order. This makes this comparator @tech{inconsistent with equality}
225+
when two sequences are not @racket[equal?] but contain equal elements in the same order.
226+
227+
@(examples
228+
#:eval (make-evaluator) #:once
229+
(equal? (list 1 2 3) (vector 1 2 3))
230+
(compare (lexicographic-comparator real<=>) (list 1 2 3) (vector 1 2 3)))}
231+
232+
202233
@section{Predefined Comparators}
203234

204235
@defthing[real<=> (comparator/c comparable-real?)]{
@@ -248,8 +279,8 @@ with equality unless otherwise stated.
248279
(eval:error (compare natural<=> 42 -10)))}
249280

250281
@defthing[string<=> (comparator/c immutable-string?)]{
251-
A @tech{comparator} that lexicographically compares immutable strings. Mutable
252-
strings are disallowed, to prevent clients from concurrently mutating a string
282+
A @tech{comparator} that @hyperlink[lexicographic-order-url]{lexicographically} compares immutable
283+
strings. Mutable strings are disallowed, to prevent clients from concurrently mutating a string
253284
while it's being compared.
254285

255286
@(examples
@@ -266,11 +297,11 @@ with equality unless otherwise stated.
266297
(compare char<=> #\a #\z))}
267298

268299
@defthing[symbol<=> (comparator/c symbol?)]{
269-
A @tech{comparator} that lexicographically compares symbols. Symbols are equivalent if they contain
270-
the same characters. Note that this comparator is @tech{inconsistent with equality}, because symbols
271-
that print the same are not necessarily equal, due to the existence of unreadable and uninterned
272-
symbols. If only interned symbols need to be compared, use @racket[interned-symbol<=>] to ensure
273-
comparisons are consistent with equality.
300+
A @tech{comparator} that @hyperlink[lexicographic-order-url]{lexicographically} compares symbols.
301+
Symbols are equivalent if they contain the same characters. Note that this comparator is
302+
@tech{inconsistent with equality}, because symbols that print the same are not necessarily equal, due
303+
to the existence of unreadable and uninterned symbols. If only interned symbols need to be compared,
304+
use @racket[interned-symbol<=>] to ensure comparisons are consistent with equality.
274305

275306
@(examples
276307
#:eval (make-evaluator) #:once

0 commit comments

Comments
 (0)