4.16 Sets
A set represents a collection of distinct elements. The following datatypes are all sets:
structures whose types implement the gen:set generic interface.
(require racket/set) | package: base |
4.16.1 Hash Sets
A hash set is a set whose elements are compared via equal?, eqv?, or eq? and partitioned via equal-hash-code, eqv-hash-code, or eq-hash-code. A hash set is either immutable or mutable; mutable hash sets retain their elements either strongly or weakly.
Like operations on immutable hash tables, “constant time” hash set operations actually require O(log N) time for a set of size N.
A hash set can be used as a stream (see Streams) and thus as a single-valued sequence (see Sequences). The elements of the set serve as elements of the stream or sequence. If an element is added to or removed from the hash set during iteration, then an iteration step may fail with exn:fail:contract, or the iteration may skip or duplicate elements. See also in-set.
Two hash sets are equal? when they use the same element-comparison procedure (equal?, eqv?, or eq?), both hold elements strongly or weakly, have the same mutability, and have equivalent elements. Immutable hash sets support effectively constant-time access and update, just like mutable hash sets; the constant on immutable operations is usually larger, but the functional nature of immutable hash sets can pay off in certain algorithms.
All hash sets implement set->stream, set-empty?, set-member?, set-count, subset?, proper-subset?, set-map, set-for-each, set-copy, set-copy-clear, set->list, and set-first. Immutable hash sets in addition implement set-add, set-remove, set-clear, set-union, set-intersect, set-subtract, and set-symmetric-difference. Mutable hash sets in addition implement set-add!, set-remove!, set-clear!, set-union!, set-intersect!, set-subtract!, and set-symmetric-difference!.
Operations on sets that contain elements that are mutated are unpredictable in much the same way that hash table operations are unpredictable when keys are mutated.
procedure
(set-equal? x) → boolean?
x : any/c
procedure
x : any/c
procedure
x : any/c
procedure
x : any/c
procedure
(set-mutable? x) → boolean?
x : any/c
procedure
x : any/c
procedure
(set v ...) → (and/c generic-set? set-equal? set?)
v : any/c
procedure
(seteqv v ...) → (and/c generic-set? set-eqv? set?)
v : any/c
procedure
(seteq v ...) → (and/c generic-set? set-eq? set?)
v : any/c
procedure
(mutable-set v ...)
→ (and/c generic-set? set-equal? set-mutable?) v : any/c
procedure
(mutable-seteqv v ...)
→ (and/c generic-set? set-eqv? set-mutable?) v : any/c
procedure
(mutable-seteq v ...)
→ (and/c generic-set? set-eq? set-mutable?) v : any/c
procedure
(weak-set v ...) → (and/c generic-set? set-equal? set-weak?)
v : any/c
procedure
(weak-seteqv v ...) → (and/c generic-set? set-eqv? set-weak?)
v : any/c
procedure
(weak-seteq v ...) → (and/c generic-set? set-eq? set-weak?)
v : any/c
procedure
(list->set lst) → (and/c generic-set? set-equal? set?)
lst : list?
procedure
(list->seteqv lst) → (and/c generic-set? set-eqv? set?)
lst : list?
procedure
(list->seteq lst) → (and/c generic-set? set-eq? set?)
lst : list?
procedure
(list->mutable-set lst)
→ (and/c generic-set? set-equal? set-mutable?) lst : list?
procedure
(list->mutable-seteqv lst)
→ (and/c generic-set? set-eqv? set-mutable?) lst : list?
procedure
(list->mutable-seteq lst)
→ (and/c generic-set? set-eq? set-mutable?) lst : list?
procedure
(list->weak-set lst)
→ (and/c generic-set? set-equal? set-weak?) lst : list?
procedure
(list->weak-seteqv lst)
→ (and/c generic-set? set-eqv? set-weak?) lst : list?
procedure
(list->weak-seteq lst) → (and/c generic-set? set-eq? set-weak?)
lst : list?
syntax
(for/set (for-clause ...) body ...+)
syntax
(for/seteq (for-clause ...) body ...+)
syntax
(for/seteqv (for-clause ...) body ...+)
syntax
(for*/set (for-clause ...) body ...+)
syntax
(for*/seteq (for-clause ...) body ...+)
syntax
(for*/seteqv (for-clause ...) body ...+)
syntax
(for/mutable-set (for-clause ...) body ...+)
syntax
(for/mutable-seteq (for-clause ...) body ...+)
syntax
(for/mutable-seteqv (for-clause ...) body ...+)
syntax
(for*/mutable-set (for-clause ...) body ...+)
syntax
(for*/mutable-seteq (for-clause ...) body ...+)
syntax
(for*/mutable-seteqv (for-clause ...) body ...+)
syntax
(for/weak-set (for-clause ...) body ...+)
syntax
(for/weak-seteq (for-clause ...) body ...+)
syntax
(for/weak-seteqv (for-clause ...) body ...+)
syntax
(for*/weak-set (for-clause ...) body ...+)
syntax
(for*/weak-seteq (for-clause ...) body ...+)
syntax
(for*/weak-seteqv (for-clause ...) body ...+)
procedure
(in-immutable-set st) → sequence?
st : set?
procedure
(in-mutable-set st) → sequence?
st : set-mutable?
procedure
(in-weak-set st) → sequence?
st : set-weak?
As with in-list and some other sequence constructors, in-immutable-set performs better when it appears directly in a for clause.
These sequence constructors are compatible with Custom Hash Sets.
4.16.2 Set Predicates and Contracts
procedure
(generic-set? v) → boolean?
v : any/c
> (generic-set? (list 1 2 3)) #t
> (generic-set? (set 1 2 3)) #t
> (generic-set? (mutable-seteq 1 2 3)) #t
> (generic-set? (vector 1 2 3)) #f
procedure
(set-implements? st sym ...) → boolean?
st : generic-set? sym : symbol?
> (set-implements? (list 1 2 3) 'set-add) #t
> (set-implements? (list 1 2 3) 'set-add!) #f
> (set-implements? (set 1 2 3) 'set-add) #t
> (set-implements? (set 1 2 3) 'set-add!) #t
> (set-implements? (mutable-seteq 1 2 3) 'set-add) #t
> (set-implements? (mutable-seteq 1 2 3) 'set-add!) #t
> (set-implements? (weak-seteqv 1 2 3) 'set-remove 'set-remove!) #t
procedure
(set-implements/c sym ...) → flat-contract?
sym : symbol?
procedure
(set/c elem/c [ #:cmp cmp #:kind kind #:lazy? lazy? #:equal-key/c equal-key/c]) → contract? elem/c : chaperone-contract? cmp : (or/c 'dont-care 'equal 'eqv 'eq) = 'dont-care
kind : (or/c 'dont-care 'immutable 'mutable 'weak 'mutable-or-weak) = 'immutable
lazy? : any/c =
(not (and (equal? kind 'immutable) (flat-contract? elem/c))) equal-key/c : contract? = any/c
If kind is 'immutable, 'mutable, or 'weak, the resulting contract accepts only hash sets that are respectively immutable, mutable with strongly-held keys, or mutable with weakly-held keys. If kind is 'mutable-or-weak, the resulting contract accepts any mutable hash sets, regardless of key-holding strength.
If cmp is 'equal, 'eqv, or 'eq, the resulting contract accepts only hash sets that compare elements using equal?, eqv?, or eq?, respectively.
If cmp is 'eqv or 'eq, then elem/c must be a flat contract.
If cmp and kind are both 'dont-care, then the resulting contract will accept any kind of set, not just hash sets.
If lazy? is not #f, then the elements of the set are not checked immediately by the contract and only the set itself is checked (according to the cmp and kind arguments). If lazy? is #f, then the elements are checked immediately by the contract. The lazy? argument is ignored when the set contract accepts generic sets (i.e., when cmp and kind are both 'dont-care); in that case, the value being checked in that case is a list?, then the contract is not lazy otherwise the contract is lazy.
If kind allows mutable sets (i.e., is 'dont-care, 'mutable, 'weak, or 'mutable-or-weak) and lazy? is #f, then the elements are checked both immediately and when they are accessed from the set.
The equal-key/c contract is used when values are passed to the comparison and hashing functions used internally.
The result contract will be a flat contract when elem/c and equal-key/c are both flat contracts, lazy? is #f, and kind is 'immutable. The result will be a chaperone contract when elem/c is a chaperone contract.
4.16.3 Generic Set Interface
syntax
> (struct binary-set [integer] #:transparent #:methods gen:set [(define (set-member? st i) (bitwise-bit-set? (binary-set-integer st) i)) (define (set-add st i) (binary-set (bitwise-ior (binary-set-integer st) (arithmetic-shift 1 i)))) (define (set-remove st i) (binary-set (bitwise-and (binary-set-integer st) (bitwise-not (arithmetic-shift 1 i)))))]) > (define bset (binary-set 5)) > bset (binary-set 5)
> (generic-set? bset) #t
> (set-member? bset 0) #t
> (set-member? bset 1) #f
> (set-member? bset 2) #t
> (set-add bset 4) (binary-set 21)
> (set-remove bset 2) (binary-set 1)
4.16.3.1 Set Methods
The methods of gen:set can be classified into three categories, as determined by their fallback implementations:
methods with no fallbacks,
methods whose fallbacks depend on other, non-fallback methods,
and methods whose fallbacks can depend on either fallback or non-fallback methods.
As an example, implementing the following methods would guarantee that all the methods in gen:set would at least have a fallback method:
There may be other such subsets of methods that would guarantee at least a fallback for every method.
procedure
(set-member? st v) → boolean?
st : generic-set? v : any/c
procedure
(set-add st v) → generic-set?
st : generic-set? v : any/c
procedure
st : generic-set? v : any/c
procedure
(set-remove st v) → generic-set?
st : generic-set? v : any/c
procedure
(set-remove! st v) → void?
st : generic-set? v : any/c
procedure
(set-empty? st) → boolean?
st : generic-set?
Supported for any st that implements set->stream or set-count.
procedure
st : generic-set?
Supported for any st that supports set->stream.
procedure
st : (and/c generic-set? (not/c set-empty?))
Supported for any st that implements set->stream.
procedure
(set-rest st) → generic-set?
st : (and/c generic-set? (not/c set-empty?))
Supported for any st that implements set-remove and either set-first or set->stream.
procedure
(set->stream st) → stream?
st : generic-set?
procedure
(set-copy st) → generic-set?
st : generic-set?
Supported for any st that supports set->stream and implements set-copy-clear and set-add!.
procedure
(set-copy-clear st) → (and/c generic-set? set-empty?)
st : generic-set?
A difference between set-copy-clear and set-clear is that the latter conceptually iterates set-remove on the given set, and so it preserves any contract on the given set. The set-copy-clear function produces a new set without any contracts.
The set-copy-clear function must call concrete set constructors and thus has no generic fallback.
procedure
(set-clear st) → (and/c generic-set? set-empty?)
st : generic-set?
Supported for any st that implements set-remove and supports set->stream.
procedure
(set-clear! st) → void?
st : generic-set?
Supported for any st that implements set-remove! and either supports set->stream or implements set-first and either set-count or set-empty?.
procedure
(set-union st0 st ...) → generic-set?
st0 : generic-set? st : generic-set?
If st0 is a list, each st must also be a list. This operation runs on lists in time proportional to the total size of the sts times the size of the result.
If st0 is a hash set, each st must also be a hash set that uses the same comparison function (equal?, eqv?, or eq?). The mutability and key strength of the hash sets may differ. This operation runs on hash sets in time proportional to the total size of all of the sets except the largest immutable set.
At least one set must be provided to set-union to determine the type of the resulting set (list, hash set, etc.). If there is a case where set-union may be applied to zero arguments, instead pass an empty set of the intended type as the first argument.
Supported for any st that implements set-add and supports set->stream.
> (set-union (set)) (set)
> (set-union (seteq)) (seteq)
> (set-union (set 1 2) (set 2 3)) (set 1 3 2)
> (set-union (list 1 2) (list 2 3)) '(3 1 2)
> (set-union (set 1 2) (seteq 2 3)) set-union: set arguments have incompatible equivalence
predicates
first set: (set 1 2)
incompatible set: (seteq 2 3)
; Sets of different types cannot be unioned
procedure
(set-union! st0 st ...) → void?
st0 : generic-set? st : generic-set?
If st0 is a hash set, each st must also be a hash set that uses the same comparison function (equal?, eqv?, or eq?). The mutability and key strength of the hash sets may differ. This operation runs on hash sets in time proportional to the total size of the sts.
Supported for any st that implements set-add! and supports set->stream.
procedure
(set-intersect st0 st ...) → generic-set?
st0 : generic-set? st : generic-set?
If st0 is a list, each st must also be a list. This operation runs on lists in time proportional to the total size of the sts times the size of st0.
If st0 is a hash set, each st must also be a hash set that uses the same comparison function (equal?, eqv?, or eq?). The mutability and key strength of the hash sets may differ. This operation runs on hash sets in time proportional to the size of the smallest immutable set.
Supported for any st that implements either set-remove or both set-clear and set-add, and supports set->stream.
procedure
(set-intersect! st0 st ...) → void?
st0 : generic-set? st : generic-set?
If st0 is a hash set, each st must also be a hash set that uses the same comparison function (equal?, eqv?, or eq?). The mutability and key strength of the hash sets may differ. This operation runs on hash sets in time proportional to the size of st0.
Supported for any st that implements set-remove! and supports set->stream.
procedure
(set-subtract st0 st ...) → generic-set?
st0 : generic-set? st : generic-set?
If st0 is a list, each st must also be a list. This operation runs on lists in time proportional to the total size of the sts times the size of st0.
If st0 is a hash set, each st must also be a hash set that uses the same comparison function (equal?, eqv?, or eq?). The mutability and key strength of the hash sets may differ. This operation runs on hash sets in time proportional to the size of st0.
Supported for any st that implements either set-remove or both set-clear and set-add, and supports set->stream.
procedure
(set-subtract! st0 st ...) → void?
st0 : generic-set? st : generic-set?
If st0 is a hash set, each st must also be a hash set that uses the same comparison function (equal?, eqv?, or eq?). The mutability and key strength of the hash sets may differ. This operation runs on hash sets in time proportional to the size of st0.
Supported for any st that implements set-remove! and supports set->stream.
procedure
(set-symmetric-difference st0 st ...) → generic-set?
st0 : generic-set? st : generic-set?
If st0 is a list, each st must also be a list. This operation runs on lists in time proportional to the total size of the sts times the size of st0.
If st0 is a hash set, each st must also be a hash set that uses the same comparison function (equal?, eqv?, or eq?). The mutability and key strength of the hash sets may differ. This operation runs on hash sets in time proportional to the total size of all of the sets except the largest immutable set.
Supported for any st that implements set-remove or both set-clear and set-add, and supports set->stream.
> (set-symmetric-difference (set 1) (set 1 2) (set 1 2 3)) (set 1 3)
procedure
(set-symmetric-difference! st0 st ...) → void?
st0 : generic-set? st : generic-set?
If st0 is a hash set, each st must also be a hash set that uses the same comparison function (equal?, eqv?, or eq?). The mutability and key strength of the hash sets may differ. This operation runs on hash sets in time proportional to the total size of the sts.
Supported for any st that implements set-remove! and supports set->stream.
procedure
st : generic-set? st2 : generic-set?
If st0 is a list, each st must also be a list. This operation runs on lists in time proportional to the size of st times the size of st2.
If st0 is a hash set, each st must also be a hash set that uses the same comparison function (equal?, eqv?, or eq?). The mutability and key strength of the hash sets may differ. This operation runs on hash sets in time proportional to the size of st plus the size of st2.
Supported for any st and st2 that both support subset?; also supported for any if st2 that implements set=? regardless of st.
> (set=? (list 1 2) (list 2 1)) #t
> (set=? (set 1) (set 1 2 3)) #f
> (set=? (set 1 2 3) (set 1)) #f
> (set=? (set 1 2 3) (set 1 2 3)) #t
> (set=? (seteq 1 2) (mutable-seteq 2 1)) #t
> (set=? (seteq 1 2) (seteqv 1 2)) set=?: set arguments have incompatible equivalence
predicates
first set: (seteq 1 2)
incompatible set: (seteqv 1 2)
; Sets of different types cannot be compared
procedure
st : generic-set? st2 : generic-set?
If st is a list, then st2 must also be a list. This operation runs on lists in time proportional to the size of st times the size of st2.
If st is a hash set, then st2 must also be a hash set that uses the same comparison function (equal?, eqv?, or eq?). The mutability and key strength of the hash sets may differ. This operation runs on hash sets in time proportional to the size of st.
Supported for any st that supports set->stream.
> (subset? (set 1) (set 1 2 3)) #t
> (subset? (set 1 2 3) (set 1)) #f
> (subset? (set 1 2 3) (set 1 2 3)) #t
procedure
(proper-subset? st st2) → boolean?
st : generic-set? st2 : generic-set?
If st is a list, then st2 must also be a list. This operation runs on lists in time proportional to the size of st times the size of st2.
If st is a hash set, then st2 must also be a hash set that uses the same comparison function (equal?, eqv?, or eq?). The mutability and key strength of the hash sets may differ. This operation runs on hash sets in time proportional to the size of st plus the size of st2.
Supported for any st and st2 that both support subset?.
> (proper-subset? (set 1) (set 1 2 3)) #t
> (proper-subset? (set 1 2 3) (set 1)) #f
> (proper-subset? (set 1 2 3) (set 1 2 3)) #f
procedure
st : generic-set?
Supported for any st that supports set->stream.
Supported for any st that supports set->stream.
procedure
(set-for-each st proc) → void?
st : generic-set? proc : (any/c . -> . any)
Supported for any st that supports set->stream.
procedure
st : generic-set?
Supported for any st that supports set->stream.
procedure
(impersonate-hash-set st inject-proc add-proc shrink-proc extract-proc [ clear-proc equal-key-proc] prop prop-val ... ...) → (and/c (or/c set-mutable? set-weak?) impersonator?) st : (or/c set-mutable? set-weak?) inject-proc : (or/c #f (-> set? any/c any/c)) add-proc : (or/c #f (-> set? any/c any/c)) shrink-proc : (or/c #f (-> set? any/c any/c)) extract-proc : (or/c #f (-> set? any/c any/c)) clear-proc : (or/c #f (-> set? any)) = #f equal-key-proc : (or/c #f (-> set? any/c any/c)) = #f prop : impersonator-property? prop-val : any/c
The inject-proc procedure is called whenever an element is temporarily put into the set for the purposes of comparing it with other elements that may already be in the set. For example, when evaluating (set-member? s e), e will be passed to the inject-proc before comparing it with other elements of s.
The add-proc procedure is called when adding an element to a set, e.g., via set-add or set-add!. The result of the add-proc is stored in the set.
The shrink-proc procedure is called when building a new set with one fewer element. For example, when evaluating (set-remove s e) or (set-remove! s e), an element is removed from a set, e.g., via set-remove or set-remove!. The result of the shrink-proc is the element actually removed from the set.
The extract-proc procedure is called when an element is pulled out of a set, e.g., by set-first. The result of the extract-proc is the element actually produced by from the set.
The clear-proc is called by set-clear and set-clear! and if it returns (as opposed to escaping, perhaps via raising an exception), the clearing operation is permitted. Its result is ignored. If clear-proc is #f, then clearing is done element by element (via calls into the other supplied procedures).
The equal-key-proc is called when an element’s hash code is needed of when an element is supplied to the underlying equality in the set. The result of equal-key-proc is used when computing the hash or comparing for equality.
If any of the inject-proc, add-proc, shrink-proc, or extract-proc arguments are #f, then they all must be #f, the clear-proc and equal-key-proc must also be #f, and there must be at least one property supplied.
Pairs of prop and prop-val (the number of arguments to impersonate-hash-set must be odd) add impersonator properties or override impersonator property values of st.
procedure
(chaperone-hash-set st inject-proc add-proc shrink-proc extract-proc [ clear-proc equal-key-proc] prop prop-val ... ...) → (and/c (or/c set? set-mutable? set-weak?) chaperone?) st : (or/c set? set-mutable? set-weak?) inject-proc : (or/c #f (-> set? any/c any/c)) add-proc : (or/c #f (-> set? any/c any/c)) shrink-proc : (or/c #f (-> set? any/c any/c)) extract-proc : (or/c #f (-> set? any/c any/c)) clear-proc : (or/c #f (-> set? any)) = #f equal-key-proc : (or/c #f (-> set? any/c any/c)) = #f prop : impersonator-property? prop-val : any/c
4.16.4 Custom Hash Sets
syntax
(define-custom-set-types name optional-predicate comparison-expr optional-hash-functions)
optional-predicate =
| #:elem? predicate-expr optional-hash-functions =
| hash1-expr | hash1-expr hash2-expr
Defines seven names:
name? recognizes instances of the new type,
immutable-name? recognizes immutable instances of the new type,
mutable-name? recognizes mutable instances of the new type with strongly-held elements,
weak-name? recognizes mutable instances of the new type with weakly-held elements,
make-immutable-name constructs immutable instances of the new type,
make-mutable-name constructs mutable instances of the new type with strongly-held elements, and
make-weak-name constructs mutable instances of the new type with weakly-held elements.
The constructors all accept a stream as an optional argument, providing initial elements.
> (define-custom-set-types string-set #:elem? string? string=? string-length)
> (define imm (make-immutable-string-set '("apple" "banana")))
> (define mut (make-mutable-string-set '("apple" "banana"))) > (generic-set? imm) #t
> (generic-set? mut) #t
> (set? imm) #t
> (generic-set? imm) #t
> (string-set? imm) #t
> (string-set? mut) #t
> (immutable-string-set? imm) #t
> (immutable-string-set? mut) #f
> (set-member? imm "apple") #t
> (set-member? mut "banana") #t
> (equal? imm mut) #f
> (set=? imm mut) #t
> (set-remove! mut "banana") > (set-member? mut "banana") #f
> (equal? (set-remove (set-remove imm "apple") "banana") (make-immutable-string-set)) #t
procedure
(make-custom-set-types eql? [ hash1 hash2 #:elem? elem? #:name name #:for who])
→
(any/c . -> . boolean?) (any/c . -> . boolean?) (any/c . -> . boolean?) (any/c . -> . boolean?) (->* [] [stream?] generic-set?) (->* [] [stream?] generic-set?) (->* [] [stream?] generic-set?)
eql? :
(or/c (any/c any/c . -> . any/c) (any/c any/c (any/c any/c . -> . any/c) . -> . any/c))
hash1 :
(or/c (any/c . -> . exact-integer?) (any/c (any/c . -> . exact-integer?) . -> . exact-integer?)) = (const 1)
hash2 :
(or/c (any/c . -> . exact-integer?) (any/c (any/c . -> . exact-integer?) . -> . exact-integer?)) = (const 1) elem? : (any/c . -> . boolean?) = (const #true) name : symbol? = 'custom-set who : symbol? = 'make-custom-set-types
The comparison function eql? may accept 2 or 3 arguments. If it accepts 2 arguments, it given two elements to compare them. If it accepts 3 arguments and does not accept 2 arguments, it is also given a recursive comparison function that handles data cycles when comparing sub-parts of the elements.
The hash functions hash1 and hash2 may accept 1 or 2 arguments. If either hash function accepts 1 argument, it is applied to a element to compute the corresponding hash value. If either hash function accepts 2 arguments and does not accept 1 argument, it is also given a recursive hash function that handles data cycles when computing hash values of sub-parts of the elements.
The predicate elem? must accept 1 argument and is used to recognize valid elements for the new set type.
Produces seven values:
a predicate recognizing all instances of the new set type,
a predicate recognizing weak instances,
a predicate recognizing mutable instances,
a predicate recognizing immutable instances,
a constructor for weak instances,
a constructor for mutable instances, and
a constructor for immutable instances.
See define-custom-set-types for an example.