289: Unordered sets and bags

by Wolfgang Corcoran-Mathe (text and test suite), Peter McGoron (sample implementation), and John Cowan (author of SRFI 113)

Status

This SRFI is currently in draft status. Here is an explanation of each status that a SRFI can hold. To provide input on this SRFI, please send email to srfi-289@nospamsrfi.schemers.org. To subscribe to the list, follow these instructions. You can access previous messages via the mailing list archive.

Abstract

Sets and bags (also known as multisets) are unordered collections that contain only objects of a type given by a collection’s SRFI 128 comparator. Every element of a set is unique (in the sense of the set’s comparator), but bags can contain any number of duplicate elements. This SRFI is a revision of SRFI 113 that fixes several infelicities and is more consistent with related SRFIs.

Table of Contents

  1. Rationale
    1. Element equality
    2. New forms
    3. Deprecated & removed SRFI 113 forms
    4. Why bother?
  2. Specification
    1. Notation
    2. Linear update
      1. Discussion
    3. Comparator restrictions
    4. Note on examples
    5. Set library
      1. Constructors
      2. Predicates
      3. Accessors
      4. Updaters
      5. The whole set
      6. Searching
    6. Mapping & folding
      1. Examples
      2. Copying & conversion
      3. Comparison
      4. Set theory operations
      5. Comparator
    7. Bag library
      1. Constructors
      2. Predicates
      3. Accessors
      4. Updaters
      5. The whole bag
      6. Searching
    8. Mapping & folding
      1. Example
      2. Copying & conversion
      3. Comparison
      4. Set theory operations
      5. Comparator
      6. Additional bag procedures
  3. Implementation
  4. Acknowledgements
  5. References
  6. Copyright

Issues

Should bag-delete-all and its relatives be renamed? In the presence of duplicate elements, “all” is ambiguous: it could be understood to refer to the elements of the bag, rather than the elements of the list argument. The procedure removes at most one element from the bag for each element in its list argument, e.g.

(bag->list
 (bag-delete-all (bag real-comparator 1 2 2 3) '(2)))
  ⇒ (1 2 3),

but its name might lead you to expect

(bag->list
 (bag-delete-all (bag real-comparator 1 2 2 3) '(2)))
  ⇒ (1 3).

Rationale

This SRFI is an elucidated revision of John Cowan’s SRFI 113. John’s post-finalization notes to that SRFI have been incorporated, and some underspecified forms and confusing details have been fixed. Bag procedures specifications are given in full, whereas in SRFI 113 they were mostly defined by analogy. In the process of writing these out, several contradictions were caught and corrected. I hope that the result of this editorial work is a SRFI that is clearer and more complete than its predecessor.

Element equality

A confusing subtlety of SRFI 113 is its occasional use of multiple notions of element equality. Several SRFI 113 procedures refer to elements that are equal in the sense of a set’s (or bag’s) comparator, but different in some other sense, implicitly that of eqv?. This is strange, considering that a set or bag may use eqv? as its equality predicate. With sets, the layering of multiple kinds of equality leads to tricky implementation details and the presence of forms (like set-replace) that are trivial unless other kinds of equality is relevant. With bags, the layering is completely unworkable, since there is no guarantee that a bag will store multiple equal elements.

This SRFI eliminates this problem by using only one notion of element equality: that given by a collection’s comparator. This simplifies things quite a bit and gives us something much closer to the mathematical concepts of set and multiset. On the downside, it is a breaking change from SRFI 113.

New forms

Most of the new names added by this SRFI are aliases of SRFI 113 forms whose original names I considered confusing. The exceptions are set-accumulate and bag-accumulate, which are modeled on oset-accumulate from SRFI 153, and the pop procedures which are based on hashmap-pop from SRFI 146.

Deprecated & removed SRFI 113 forms

Some forms specified by SRFI 113 are deprecated in this SRFI. A few irreparable forms have been removed. The following list gives some reasons for these changes.

set-member
bag-member

These accessors are coherent but fairly useless. Unless you are using multiple notions of equality (which is itself deprecated usage), they simply return one of their arguments. Which depends on whether the collection argument contains the given element.

set-replace
set-replace!
bag-replace (removed)
bag-replace! (removed)

Like the member procedures, replace is a trivial operation unless the notion of element equality is being abused. bag-replace was underspecified; its intended semantics are a mystery, given that it may be impossible to replace a specific “copy” of an element that appears multiple times. It has been removed.

set-search!
bag-search!

These complex procedures can be difficult both to implement and to use. They also iterate over a collection while simultaneously building a copy of it, which can entail unexpected performance costs when only the iteration is needed.

list->set!
list->bag!

These were arguably miscategorized as conversions. I prefer the aliases set-adjoin-all! and bag-adjoin-all!.

Why bother?

The effective exploitation of his powers of abstraction must be regarded as one of the most vital activities of a competent programmer.

— Edsger W. Dijkstra, The Humble Programmer (1972)

There is some question whether we should use sets or bags in our code instead of more concrete types like hash-tables or binary trees. My answer is we should, for the same reason we often use map instead of explicit recursion: it makes our intentions clearer. Sets and bags have clear semantics which may be vaguely familiar to most programmers from set-theory studies. The meaning of a hash-table in a given program, on the other hand, is not obvious: it may act as a set, bag, or something else entirely. The only way to know is to read every part of the program that interacts with it. Judicious use of abstract types like those presented in this SRFI can make programs easier to understand.

Specification

The terms must, may, etc., though not capitalized in this document, should be interpreted according to RFC 2119.

This SRFI provides two Scheme libraries, (srfi 289 set) and (srfi 289 bag). Theses libraries may be provided under the usual aliases, e.g. (srfi :289 set), (srfi srfi-289 set), etc.

Sets and bags form new types as if they were created by define-record-type (R7RS §5.5). Values of both types use a specific SRFI 128 comparator and contain elements satisfying that comparator’s type-test predicate. Sets never contains duplicate elements (in the sense of their comparator’s equality predicate). Bags, on the other hand, may contain duplicates, but they are not required to store each duplicate object explicitly.

Except as noted, whenever two elements of a set or bag are equal in the sense of the collection’s comparator, those elements are considered to be the same for all purposes. For example, since the two strings in the following expression are the same according to the new set’s comparator, it is unspecified which one set adds:

(let ((comp (make-comparator string? string=? #f string-hash))
      (s1 (string-copy "hello"))
      (s2 (string-copy "hello")))
  (eqv? s1 (car (set->list (set comp s1 s2)))))
  ⇒ #t or #f

The procedures of this SRFI that do not observe this rule are deprecated.

Notation

The argument-naming conventions of this document are consistent with those of the R7RS. The following naming conventions also imply type restrictions:

bag
bag1, …, bagj, …
bag (satisfies bag?)
comparator
SRFI 128 comparator object
element
element of a set or bag (satisfies a relevant type-test predicate indicated by context)
pred
predicate procedure (see R7RS §1.3.5)
set
set1, …, setj, …
set (satisfies set?)

Many procedures in this SRFI begin with a paragraph describing further argument restrictions. If any of the requirements of these paragraphs are violated, it is an error.

Linear update

Like several other SRFIs, this SRFI defines linear-update procedures, all of whose names end in ‘!’. These procedures are allowed, but not required, to side-effect one of their arguments in order to construct their results. (Unlike similarly-named procedures in the Scheme Reports, these forms return meaningful values.) An implementation may implement linear-update procedures as side-effecting or side-effect-free procedures, depending on what makes the most sense for the underlying representation.

A linear-update procedure is permitted to mutate one of its arguments, and it is an error to reference that value after the procedure has been invoked on it. For example, the following is an error:

(let* ((s (set (make-equal-comparator) 1 2 3))
       (s* (set-adjoin! s 4)))
  (set->list s))

This, however, is well-defined:

(let ((s (set (make-equal-comparator) 1 2 3)))
  (set->list (set-adjoin! s 4)))

In other words, a user of a linear-update procedures should write in a functional style, but should additionally be sure that there are no “live” references to the value that the procedure is allowed to mutate when the procedure is called.

Discussion

This section is informational.

Linear-update procedures are confusing and there is every reason to believe they will be misused. The original test suite for SRFI 113, for example, incorrectly uses linear-update forms like set-adjoin! as simple mutators. Despite this, they have been retained in the interest of backward compatibility with SRFI 113 and alignment with the R7RS Large project’s decision to preserve linear-update forms in their specifications.

By encompassing both transient and persistent set implementations, SRFI 113 created a problem for programmers wishing to write efficient portable code. Should they assume a persistent implementation and use only functional forms (e.g. set-adjoin), or should they use the linear-update alternatives, on the assumption of a transient implementation? The best answer is to have different interfaces for mutable and immutable collections. But, stuck with a single interface as we are, a good approach may be to use the linear-update forms exclusively in portable code. Hash-table implementations of sets and bags are very common, and they perform best with linear-update code. Under a persistent implementation, on the other hand, the linear-update forms will likely be aliases for their functional counterparts, so code using them should incur no extra performance costs.

Comparator restrictions

A comparator used by a set or bag must provide an equality procedure. (See SRFI 128.) Implementations of this SRFI may additionally require that comparators provide a hash procedure, but they must not require an ordering procedure.

Note on examples

The examples in this SRFI are non-normative: if an example contradicts the specification text, the text is right. They use comparators (especially real-comparator) defined in SRFI 162.

Set library

The identifiers in this section are exported by the (srfi 289 set) library.

Constructors

(set comparator element …) → set

It is an error if any of the element arguments does not satisfy comparator’s type-test predicate.

Returns a newly allocated set using comparator and containing the given elements.

Examples
(set->list (set real-comparator 1 2 3))
  ⇒ (1 3 2) ; order unspecified

(set->list (set eqv-comparator 'foo 'foo 'bar))
  ⇒ (foo bar) ; order unspecified

(set-unfold stop? mapper successor seed comparator) → set

seed may be any object. stop? is a predicate taking a single value of seed’s type. mapper and successor are procedures taking a single value of seed’s type and returning a single value. It is an error if mapper returns a value that does not satisfy comparator’s type-test predicate.

Returns a newly allocated set using comparator. The elements of this set are generated by the following process:

  1. The new set is initialized to the empty set.

  2. stop? is applied to the current seed. If the result is true, then the unfolding process halts and the new set is returned. Otherwise, the process continues.

  3. The result of applying mapper to the current seed is added to the set.

  4. successor is applied to the current seed to produce the next seed. Unfolding continues with step 2.

If stop?, mapper, or successor mutate the values returned by mapper, then set-unfold returns a set whose contents are unspecified. If multiple returns occur from set-unfold, the values returned by earlier returns are not mutated.

Example
(set->list
 (set-unfold (lambda (i) (= i 4))
             (lambda (i) (make-string i #\a))
             (lambda (i) (+ i 1))
             0
             string-comparator))
  ⇒ ("a" "aaa" "aa" "") ; order unspecified

(set-accumulate proc comparator seed1 seed2 …) → set

The seeds may be any objects. proc must accept n arguments, where n is the number of seeds, and return n + 1 values, the first of which must satisfy comparator’s type-test predicate.

Similar to set-unfold, except that a single procedure controls the unfolding of a new set. Let n be the number of seeds:

  1. The new set is initialized to the empty set.

  2. Proc is applied to a procedure terminate and the seeds, in that order. If terminate is invoked in tail context on any number of arbitrary values, then the process halts and the new set along with those values are returned from set-accumulate.

  3. Otherwise, proc is expected to return n + 1 values: a new set element and n new seeds. The element is added to the new set and the process continues at step two with the new seeds.

It is an error for the number of seeds to vary between steps of the unfolding process. If proc mutates values returned by earlier invocations of proc, then set-accumulate returns a set whose contents are unspecified. If multiple returns occur from set-accumulate, the values returned by earlier returns are not mutated.

Example
(let-values (((set sym)
              (set-accumulate (lambda (terminate i)
                                (if (> i 3)
                                    (terminate 'finished)
                                    (values i (+ i 1))))
                              (make-real-comparator)
                              1)))
  (values (set->list set) sym))
  ⇒ (1 2 3)  ; or some permutation thereof
    finished

Predicates

(set? obj) → boolean

Returns #t if obj is a set, and #f otherwise.

(set-contains? set element) → boolean

Returns #t if element is a member of set and #f otherwise.

Examples
(set-contains? (set real-comparator 2 4 6) 2) ⇒ #t
(set-contains? (set real-comparator 1 3 5) 2) ⇒ #f

(set-empty? set) → boolean

Returns #t if set has no elements and #f otherwise.

Examples
(set-empty? (set real-comparator)) ⇒ #t
(set-empty? (set real-comparator 1 3 5)) ⇒ #f

(set-disjoint? set1 set2) → boolean

It is an error if the comparator of set1 is not the same (in the sense of eqv?) as that of set2.

Returns #t if set1 and set2 have no elements in common and #f otherwise.

Examples
(set-disjoint? (set real-comparator 1 3 5)
               (set real-comparator 2 4 6))
  ⇒ #t

(set-disjoint? (set real-comparator 1 3 4)
               (set real-comparator 2 4 6))
  ⇒ #f

Accessors

[Deprecated] (set-member set element default) → obj

Default may be any Scheme object.

Returns the element of set that is equal (in the sense of set’s comparator) to element. If element is not a member of set, default is returned instead.

Note

This section is informational.

Unless a notion of element equality other than that given by set’s comparator is relevant, this procedure is equivalent to

(lambda (set element default)
  (if (set-contains? set element) element default))

(set-element-comparator set) → comparator

Returns the comparator used to compare the elements of set.

Updaters

(set-adjoin set element …) → set

Returns a newly allocated set that uses the same comparator as set and that contains all its elements in addition to the element arguments.

Example
(set->list (set-adjoin (set real-comparator 0) 1 2))
  ⇒ (1 2 0) ; order unspecified

(set-adjoin! set element …) → set

A linear-update variant of set-adjoin. It is allowed, but not required, to mutate set to produce its result.

(set-adjoin-all set list) → set

Every element of list must satisfy set’s comparator’s type-test predicate.

Returns a newly allocated set that uses the same comparator as set and that contains all its elements in addition to the elements of list.

Example
(set->list (set-adjoin-all (set real-comparator 0)
                           '(0 1 2)))
  ⇒ (1 2 0) ; order unspecified

(set-adjoin-all! set list) → set

A linear-update variant of set-adjoin-all. It is allowed, but not required, to mutate set to produce its result.

[Deprecated] (set-replace set element) → set

Returns a newly allocated set that uses the same comparator as set and contains all its elements except as follows: If element is equal (in the sense of set’s comparator) to an existing member of set, then that member is omitted and replaced by element. If there is no such element in set, then set is returned unchanged.

Note

This section is informational.

Strictly speaking, this procedure does nothing. It only makes sense to use when some notion of element equality other than that defined by set’s comparator is in play.

[Deprecated] (set-replace! set element) → set

A linear-update variant of set-replace. It is allowed, but not required, to mutate set to produce its result.

(set-delete set element …) → set

Returns a newly allocated set containing all the elements of set except for any that are equal (in the sense of set’s comparator) to one or more of the elements.

Examples
(set->list (set-delete (set real-comparator 1 2 3) 2 1))
  ⇒ (3)

(set->list (set-delete (set real-comparator 4 6) 2 3))
  ⇒ (4 6) ; order unspecified

(set-delete! set element …) → set

A linear-update variant of set-delete. It is allowed, but not required, to mutate set to produce its result.

(set-delete-all set list) → set

It is an error if any element of list does not satisfy the type-test predicate of set’s comparator.

Returns a newly allocated set containing all the elements of set except for any that are equal (in the sense of set’s comparator) to one or more elements of list.

Example
(set->list (set-delete-all (set real-comparator 1 2 3)
                           '(2 1)))
  ⇒ (3)

(set-delete-all! set list) → set

A linear-update variant of set-delete-all. It is allowed, but not required, to mutate set to produce its result.

[Deprecated] (set-search! set element failure success)

The set is searched for element. If it is not found, then the failure procedure is tail-called with two continuation arguments, insert and ignore, and is expected to tail-call one of them. If element is found, then the success procedure is tail-called with the matching element of set and two continuations, update and remove, and is expected to tail-call one of them.

The effects of the continuations are as follows (where obj is any Scheme object):

In all cases, two values are returned: the possibly updated set and obj.

(set-pop set [failure]) → obj …

If failure is provided, it must be a procedure accepting zero arguments. Otherwise, it is an error.

Chooses an arbitrary element x of set and returns two values: a newly allocated set using the same comparator as set and containing all of its elements except x, and x itself. If set is empty and failure is provided, then failure is invoked in tail context on zero arguments. Otherwise, it is an error.

Examples
(let-values (((rest x) (set-pop (set real-comparator 1 2 3))))
  (values (set->list rest) x))
  ⇒ (1 3)  ; possibly
    2

(set-pop! set [failure]) → obj …

Linear-update variant of set-pop which is allowed, but not required, to mutate set to produce its result.

The whole set

(set-size set) → exact-integer

Returns the number of elements in set.

Examples
(set-size (set real-comparator 1 2)) ⇒ 2

(set-size (set real-comparator)) ⇒ 0

Searching

(set-find pred set failure) → obj …

Returns an arbitrarily-chosen element of set that satisfies pred. If no such element exists, then failure is invoked in tail context on zero arguments.

Examples

(set-find even?
          (set real-comparator 1 2 3 4)
          (lambda () #f))
  ⇒ 2  ; or 4

(set-find negative?
          (set real-comparator 1 2 3 4)
          (lambda () #f))
  ⇒ #f

(set-count pred set) → exact-integer

Returns the number of elements of set that satisfy pred.

Example
(set-count even? (set real-comparator 1 2 3))
  ⇒ 1

(set-any? pred set) → boolean

Returns #t if any element of set satisfies pred. Otherwise, returns #f.

Examples
(set-any? even? (set real-comparator 1 2 3 4))
  ⇒ #t

(set-any? negative? (set real-comparator 1 2 3 4))
  ⇒ #f

(set-every? pred set) → boolean

Returns #t if every element of set satisfies pred. Otherwise, returns #f.

Examples
(set-every? integer? (set real-comparator 1 2 3 4))
  ⇒ #t

(set-every? even? (set real-comparator 1 2 3 4))
  ⇒ #f

Mapping & folding

(set-map proc comparator set) → set

Proc must be a procedure accepting one argument, an element of set, and returning one value which must satisfy comparator’s type-test predicate.

Applies proc to each element of set in an arbitrary order and returns a newly allocated set, created as if by (set comparator), which contains the results of the applications.

Examples

(set-map symbol->string
         string-comparator
         (set eqv-comparator 'foo 'bar 'baz))
  ≡ (set string-comparator "foo" "bar" "baz")

(set-map (lambda (x) (quotient x 2))
         real-comparator
         (set real-comparator 1 2 3 4 5))
  ≡ (set real-comparator 0 1 2)

(set-for-each proc set) → unspecified

Proc must be a procedure accepting one argument, an element of set.

Applies proc to the elements of set in an arbitrary order, discarding the returned values. Returns an unspecified result.

Example
(let ((sum 0))
  (set-for-each (lambda (k) (set! sum (+ sum k)))
                (set real-comparator 1 2 3 4 5))
  sum) ⇒ 15

(set-fold proc nil set) → obj

Proc must be a procedure accepting two arguments and returning a single value. Nil may be any Scheme object.

Invokes proc on each member of set in an arbitrary order, passing the result of the previous invocation as a second argument. For the first invocation, nil is used as the second argument. Returns the result of the last invocation, or nil if proc was not invoked.

Example
(set-fold cons '() (set real-comparator 1 2 3))
  ⇒ (2 3 1)  ; order unspecified

(set-filter pred set) → set

Returns a newly allocated set with the same comparator as set and containing just the elements of set that satisfy pred.

Example
(set->list
 (set-filter even? (set real-comparator 1 2 3 4)))
  ⇒ (4 2)  ; order unspecified

(set-filter! pred set) → set

A linear-update variant of set-filter. It is allowed, but not required, to mutate set to produce its result.

(set-remove pred set) → set

Returns a newly allocated set with the same comparator as set and containing just the elements of set that do not satisfy pred.

Example
(set->list
 (set-remove even? (set real-comparator 1 2 3 4)))
  ⇒ (1 3)  ; order unspecified

(set-remove! pred set) → set

A linear-update variant of set-remove. It is allowed, but not required, to mutate set to produce its result.

(set-partition pred set) → set, set

Returns two values: a newly allocated set with the same comparator as set that contains just the elements of set that satisfy pred, and another newly allocated set, also with the same comparator, that contains just the elements of set that do not satisfy pred.

Examples
(let-values (((evens odds)
              (set-partition even? (set real-comparator 1 2 3 4))))
  (values (set->list evens) (set->list odds)))
  ⇒ (2 4)  ; order unspecified
    (1 3)

(set-partition! pred set) → set, set

A linear-update variant of set-partition. It is allowed, but not required, to mutate set to produce its results.

Copying & conversion

(set-copy set) → set

Returns a newly allocated set containing the elements of set and using the same comparator.

(set->list set) → list

Returns a newly allocated list containing the members of set in unspecified order.

(list->set comparator list) → set

Every element of list must satisfy comparator’s type-test predicate.

Returns a newly allocated set using comparator and containing the elements of list.

Examples
(set->list (list->set real-comparator '(1 2 3)))
  ⇒ (1 3 2)  ; order unspecified

(set->list (list->set eqv-comparator '(foo foo bar)))
  ⇒ (foo bar)  ; order unspecified

[Deprecated] (list->set! set list) → set

Every element of list must satisfy set’s comparator’s type-test predicate.

Alias of set-adjoin-all!.

Comparison

It is an error to pass sets to these predicates that do not share the same comparator (in the sense of eqv?).

None of these predicates produces a total order on sets. In particular, sets do not have the trichotomy property under set=?, set<?, and set>?: two sets may fail to satisfy all three of these predicates.

(set=? set1 set2 …) → boolean

Returns #t if each set contains the same elements.

(set<? set1 set2 …) → boolean

Returns #t if each set other than the last is a proper subset of the following set, and #f otherwise.

Examples
(set<? (set real-comparator 4 2)
       (set real-comparator 2 4 6)
       (set real-comparator 2 4 6 8))
  ⇒ #t

(set<? (set real-comparator 4 2)
       (set real-comparator 4 5 6))
  ⇒ #f

(set<? (set real-comparator 4 2)) ⇒ #t

(set>? set1 set2 …) → boolean

Returns #t if each set other than the last is a proper superset of the following set, and #f otherwise.

Examples
(set>? (set real-comparator 2 4 6 8)
       (set real-comparator 2 4 6)
       (set real-comparator 4 2))
  ⇒ #t

(set>? (set real-comparator 4 5 6)
          (set real-comparator 4 2))
  ⇒ #f

(set>? (set real-comparator 4 2)) ⇒ #t

(set<=? set1 set2 …) → boolean

Returns #t if each set other than the last is a subset of the following set, and #f otherwise.

Example
(set<=? (set real-comparator 4 2)
        (set real-comparator 2 4 6)
        (set real-comparator 6 2 4))
  ⇒ #t

(set>=? set1 set2 …) → boolean

Returns #t if each set other than the last is a superset of the following set, and #f otherwise.

(set>=? (set real-comparator 6 2 4)
        (set real-comparator 2 4 6)
        (set real-comparator 4 2))
  ⇒ #t

Set theory operations

(set-union set1 set2 …) → set

(set-intersection set1 set2 …) → set

(set-difference set1 set2 …) → set

(set-xor set1 set2) → set

The set arguments must all use the same comparator (in the sense of eqv?). Otherwise, it is an error.

Return a newly allocated set that is the union, intersection, asymmetric difference, or symmetric difference of the sets. Asymmetric difference is extended to more than two sets by taking the difference between the first set and the union of the others.

Examples
(set->list (set-union (set real-comparator 1 3 5)
                      (set real-comparator 0 2 4)))
  ⇒ (0 5 3 2 1 4)  ; order unspecified

(set->list (set-intersection (set real-comparator 1 3 2)
                             (set real-comparator 1 7 2)))
  ⇒ (1 2)  ; order unspecified

(set->list (set-difference (set real-comparator 1 3 5 7)
                           (set real-comparator 1 4 6)
                           (set real-comparator 2 5 9)))
  ⇒ (7 3)  ; order unspecified

(set->list (set-xor (set real-comparator 1 4 5 7)
                    (set real-comparator 1 4 6)))
  ⇒ (5 7 6)  ; order unspecified

(set-union! set1 set2 …) → set

(set-intersection! set1 set2 …) → set

(set-difference! set1 set2 …) → set

(set-xor! set1 set2) → set

These are linear-update variants of the set-theoretical procedures given above. They are allowed, but not required, to mutate set1 to produce their results.

Comparator

set-comparator

SRFI 128 comparator used to compare sets, which also allows sets of sets, etc. to be constructed. Set-comparator is required to provide at least type-test and equality functions. It is an error to compare sets with different comparators (in the sense of eqv?).

Bag library

The identifiers in this section are exported by the (srfi 289 bag) library.

Constructors

(bag comparator element …) → bag

Returns a newly allocated bag using comparator and containing the given elements. It is an error if any of the element arguments does not satisfy comparator’s type-test predicate.

Examples
(bag->list (bag real-comparator)) ⇒ ()

(bag->list (bag real-comparator 1 2 2 3))
  ⇒ (2 3 1 2)  ; order unspecified

(bag-unfold stop? mapper successor seed comparator) → bag

seed may be any object. stop? is a predicate taking a single value of seed’s type. mapper and successor are procedures taking a single value of seed’s type and returning a single value. It is an error if mapper returns a value that does not satisfy comparator’s type-test predicate.

Returns a newly allocated bag using comparator. The elements of this bag are generated by the following process:

  1. The new bag is initialized to the empty bag.

  2. stop? is applied to the current seed. If the result is true, then the unfolding process halts and the new bag is returned. Otherwise, the process continues.

  3. The result of applying mapper to the current seed is added to the bag.

  4. successor is applied to the current seed to produce the next seed. Unfolding continues with step 2.

If stop?, mapper, or successor mutate the values returned by mapper, then bag-unfold returns a bag whose contents are unspecified. If multiple returns occur from bag-unfold, the values returned by earlier returns are not mutated.

Example
(bag->list
 (bag-unfold (lambda (i) (> i 2))
             abs
             (lambda (i) (+ i 1))
             -2
             real-comparator))
  ⇒ (2 1 0 2 1) ; order unspecified

(bag-accumulate proc comparator seed1 seed2 …) → bag

The seeds may be any objects. proc must accept n arguments, where n is the number of seeds, and return n + 1 values, the first of which must satisfy comparator’s type-test predicate.

Similar to bag-unfold, except that a single procedure controls the unfolding of a new bag. Let n be the number of seeds:

  1. The new bag is initialized to the empty bag.

  2. Proc is applied to a procedure terminate and the seeds, in that order. If terminate is invoked in tail context on any number of arbitrary values, then the process halts and the new bag along with those values are returned from bag-accumulate.

  3. Otherwise, proc is expected to return n + 1 values: a new bag element and n new seeds. The element is added to the new bag and the process continues at step two with the new seeds.

It is an error for the number of seeds to vary between steps of the unfolding process. If proc mutates values returned by earlier invocations of proc, then bag-accumulate returns a bag whose contents are unspecified. If multiple returns occur from bag-accumulate, the values returned by earlier returns are not mutated.

Example
(let-values (((bag sym)
              (bag-accumulate (lambda (terminate i)
                                (if (> i 2)
                                    (terminate 'finished)
                                    (values (abs i) (+ i 1))))
                              (make-real-comparator)
                              -2)))
  (values (bag->list bag) sym))
  ⇒ (2 0 2 1 1)  ; or some permutation thereof
    finished

Predicates

(bag? obj) → boolean

Returns #t if obj is a bag, and #f otherwise.

(bag-contains? bag element) → boolean

Returns #t if element is a member of bag and #f otherwise.

Examples
(bag-contains? (bag real-comparator 1 3 3) 3) ⇒ #t

(bag-contains? (bag real-comparator 0 2 2) 3) ⇒ #f

(bag-empty? bag) → boolean

Returns #t if bag has no elements and #f otherwise.

(bag-disjoint? bag1 bag2) → boolean

It is an error if the comparator of bag1 is not the same (in the sense of eqv?) as that of bag2.

Returns #t if bag1 and bag2 have no elements in common and #f otherwise.

Examples
(bag-disjoint? (bag real-comparator 1 3 3)
               (bag real-comparator 2 6 6 4))
  ⇒ #t

(bag-disjoint? (bag real-comparator 1 3 3)
               (bag real-comparator 3 4 6))
  ⇒ #f

Accessors

[Deprecated] (bag-member bag element default) → obj

Default may be any Scheme object.

Returns an element of bag that is equal (in the sense of bag’s comparator) to element. If element is not a member of bag, default is returned instead.

Note

This section is informational.

Unless a notion of element equality other than that given by bag’s comparator is relevant, this procedure is equivalent to

(lambda (bag element default)
  (if (bag-contains? bag element) element default)).

Even when such a notion is relevant, note that it is unspecified which “copy” of element is returned if bag contains more than one. (Under many implementations, the notion of distinct copies may have no meaning.) Either avoid bag-member or treat it as strictly equivalent to the above procedure.

(bag-element-comparator bag) → comparator

Returns the comparator used to compare the elements of bag.

Updaters

(bag-adjoin bag element …) → bag

Returns a newly allocated bag that uses the same comparator as bag and that contains all its elements in addition to the element arguments.

Example
(bag->list (bag-adjoin (bag real-comparator 1 2) 3 4 3))
  ⇒ (1 3 2 4 3)  ; order unspecified

(bag-adjoin! bag element …) → bag

A linear-update variant of bag-adjoin. It is allowed, but not required, to mutate bag to produce its result.

(bag-adjoin-all bag list) → bag

Every element of list must satisfy bag’s comparator’s type-test predicate.

Returns a newly allocated bag that uses the same comparator as bag and that contains all its elements in addition to the elements of list.

Example
(bag->list (bag-adjoin-all (bag real-comparator 1 2)
                           '(3 4 3)))
  ⇒ (1 3 2 4 3)  ; order unspecified

(bag-adjoin-all! bag list) → bag

A linear-update variant of bag-adjoin-all. It is allowed, but not required, to mutate bag to produce its result.

(bag-delete bag element …) → bag

Returns a newly allocated bag containing all the elements of bag but with the elements removed. More precisely, if bag-delete is passed n copies of an element e, then the element count of e in bag is reduced by n to a minimum of zero.

Examples
(bag->list
 (bag-delete (bag real-comparator 1 2 2 3) 2 3))
  ⇒ (1 2)

(bag->list (bag-delete (bag real-comparator 2 4 5) 2 6))
  ⇒ (4 5)

(bag-delete! bag element …) → bag

A linear-update variant of bag-delete. It is allowed, but not required, to mutate bag to produce its result.

(bag-delete-all bag list) → bag

Every element of list must satisfy the type-test predicate of bag’s comparator. Otherwise, it is an error.

Returns a newly allocated bag containing all the elements of bag but with the elements of list removed. More precisely, if list contains n copies of an element e, then the element count of e in bag is reduced by n to a minimum of zero.

Examples
(bag->list
 (bag-delete-all (bag real-comparator 1 2 2 3) '(2 3)))
  ⇒ (1 2)

(bag->list (bag-delete (bag real-comparator 2 4 5) '(2 6)))
  ⇒ (4 5)

(bag-delete-all! bag list) → bag

A linear-update variant of bag-delete-all. It is allowed, but not required, to mutate bag to produce its result.

[Deprecated] (bag-search! bag element failure success)

The bag is searched for element. If it is not found, then the failure procedure is tail-called with two continuation arguments, insert and ignore, and is expected to tail-call one of them. If element is found, then the success procedure is tail-called with a matching element of bag and two continuations, update and remove, and is expected to tail-call one of them.

The effects of the continuations are as follows (where obj is any Scheme object):

In all cases, two values are returned: the possibly updated bag and obj.

(bag-pop bag [failure]) → obj …

If failure is provided, it must be a procedure accepting zero arguments. Otherwise, it is an error.

Chooses an arbitrary element x of bag and returns two values: a newly allocated bag using the same comparator as bag and containing all of its elements except x, and x itself. If bag is empty and failure is provided, then failure is invoked in tail context on zero arguments. Otherwise, it is an error.

Examples
(let-values (((rest x) (bag-pop (bag real-comparator 1 2 2 3))))
  (values (bag->list rest) x))
  ⇒ (1 2 3)  ; possibly
    2

(bag-pop! bag [failure]) → obj …

Linear-update variant of bag-pop which is allowed, but not required, to mutate bag to produce its result.

The whole bag

(bag-size bag) → exact-integer

Returns the number of elements in bag.

Example
(bag-size (bag real-comparator 1 2 2 3)) ⇒ 4

Searching

(bag-find pred bag failure) → obj …

Returns an arbitrarily-chosen element of bag that satisfies pred. If no such element exists, then failure is invoked in tail context on zero arguments.

Examples

(bag-find even?
          (bag real-comparator 1 2 3 4 2)
          (lambda () #f))
  ⇒ 2  ; or 4

(bag-find negative?
          (bag real-comparator 1 2 3 4 2)
          (lambda () #f))
  ⇒ #f

(bag-count pred bag) → exact-integer

Returns the number of elements of bag that satisfy pred.

Example
(bag-count even? (bag real-comparator 1 2 3))
  ⇒ 1

(bag-any? pred bag) → boolean

Returns #t if any element of bag satisfies pred. Otherwise, returns #f.

Examples
(bag-any? even? (bag real-comparator 1 2 3))
  ⇒ #t

(bag-any? negative? (bag real-comparator 1 2 3 4))
  ⇒ #f

(bag-every? pred bag) → boolean

Returns #t if every element of bag satisfies pred. Otherwise, returns #f.

Examples
(bag-every? integer? (bag real-comparator 1 2 3 4))
  ⇒ #t

(bag-every? even? (bag real-comparator 1 2 3 4))
  ⇒ #f

Mapping & folding

(bag-map proc comparator bag) → bag

Proc must be a procedure accepting one argument, an element of bag, and returning one value which must satisfy comparator’s type-test predicate.

Applies proc to each element of bag in an arbitrary order and returns a newly allocated bag, created as if by (bag comparator), which contains the results of the applications.

Example

(bag->list
 (bag-map abs (bag real-comparator -2 -1 0 1 2)))
  ⇒ (0 1 1 2 2)  ; order unspecified

(bag-for-each proc bag) → unspecified

Proc must be a procedure accepting one argument, an element of bag.

Applies proc to the elements of bag in an arbitrary order, discarding the returned values. Returns an unspecified result.

Example
(let ((sum 0))
  (bag-for-each (lambda (k) (set! sum (+ sum k)))
                (bag real-comparator 1 2 3 3 4))
  sum) ⇒ 13

(bag-fold proc nil bag) → obj

Proc must be a procedure accepting two arguments and returning a single value. Nil may be any Scheme object.

Invokes proc on each member of bag in an arbitrary order, passing the result of the previous invocation as a second argument. For the first invocation, nil is used as the second argument. Returns the result of the last invocation, or nil if proc was not invoked.

Example
(bag-fold cons '() (bag real-comparator 1 1 2))
  ⇒ (1 2 1)  ; order unspecified

(bag-filter pred bag) → bag

Returns a newly allocated bag with the same comparator as bag and containing just the elements of bag that satisfy pred.

Example
(bag->list
 (bag-filter even? (bag real-comparator 1 1 2 2 3 4 4)))
  ⇒ (2 4 2 4)  ; order unspecified

(bag-filter! pred bag) → bag

A linear-update variant of bag-filter. It is allowed, but not required, to mutate bag to produce its result.

(bag-remove pred bag) → bag

Returns a newly allocated bag with the same comparator as bag and containing just the elements of bag that do not satisfy pred.

Example
(bag->list
 (bag-remove even? (bag real-comparator 1 1 2 2 3 4 4)))
  ⇒ (1 1 3)  ; order unspecified

(bag-remove! pred bag) → bag

A linear-update variant of bag-remove. It is allowed, but not required, to mutate bag to produce its result.

(bag-partition pred bag) → bag, bag

Returns two values: a newly allocated bag with the same comparator as bag that contains just the elements of bag that satisfy pred, and another newly allocated bag, also with the same comparator, that contains just the elements of bag that do not satisfy pred.

Examples
(let-values (((evens odds)
              (bag-partition even?
                             (bag real-comparator 1 1 2 2 3 4 4))))
  (values (bag->list evens) (bag->list odds)))
  ⇒ (2 4 2 4)  ; order unspecified
    (1 3 3)

(bag-partition! pred bag) → bag, bag

A linear-update variant of bag-partition. It is allowed, but not required, to mutate bag to produce its results.

Copying & conversion

(bag-copy bag) → bag

Returns a newly allocated bag containing the elements of bag and using the same comparator.

(bag->list bag) → list

Returns a newly allocated list containing the members of bag in unspecified order.

(list->bag comparator list) → bag

It is an error if any element of list does not satisfy comparator’s type-test predicate.

Returns a newly allocated bag using comparator and containing the elements of list.

Example
(bag->list
 (list->bag real-comparator '(1 3 3 3)))
  ⇒ (3 1 3 3)  ; order unspecified

[Deprecated] (list->bag! bag list) → bag

Every element of list must satisfy bag’s comparator’s type-test predicate.

Alias of bag-adjoin-all!.

(bag->set bag) → set

Returns a newly allocated set using the same comparator as bag and containing all its unique elements.

Example
(set->list
 (bag->set (bag real-comparator 1 1 2 3 3 3)))
  ⇒ (1 3 2)  ; order unspecified

(set->bag set) → bag

Returns a newly allocated bag using the same comparator as set and containing the same elements.

Example
(bag->list
 (set->bag (set real-comparator 1 2 3)))
  ⇒ (1 2 3)  ; order unspecified

(set->bag! bag set) → bag

Returns a bag using the same comparator as bag and containing the same elements, in addition to each element of set. set->bag! is permitted, but not required, to mutate bag to produce its result.

Example
(bag->list
 (set->bag! (bag real-comparator 1 2 2)
            (set real-comparator 2 3 4)))
  ⇒ (1 2 3 2 4 2)  ; order unspecified

(bag->alist bag) → alist

Returns a newly allocated list of pairs. Each pair’s car is a unique element of bag and its cdr is the number of occurrences (an exact integer) of that element. The order of the list is unspecified.

Example
(bag->alist
 (bag real-comparator 1 1 2 2 2 3))
  ⇒ ((1 . 2) (3 . 1) (2 . 3))  ; order unspecified

(alist->bag comparator alist) → bag

Each element of alist must be a pair whose car satisfies comparator’s type-test predicate and whose cdr is a non-negative exact integer. Otherwise, it is an error.

Returns a newly allocated bag using comparator, where the keys of alist specify the elements and the corresponding values specify how many times they occur.

Example
(bag->list
 (alist->bag real-comparator
             '((1 . 2) (2 . 3) (3 . 1))))
  ⇒ (1 2 1 2 3 2)  ; order unspecified

Comparison

It is an error to pass bags to these predicates that do not share the same comparator (in the sense of eqv?).

None of these predicates produces a total order on bags. In particular, bags do not have the trichotomy property under bag=?, bag<?, and bag>?: two bags may fail to satisfy all three of these predicates.

(bag=? bag1 bag2 …) → boolean

Returns #t if each bag contains the same elements.

Examples
(bag=? (bag real-comparator 1 2 3 3) (bag real-comparator 2 3 1 3))
  ⇒ #t

(bag=? (bag real-comparator 1 3 2) (bag real-comparator 1 3 3 2))
  ⇒ #f

(bag=? (bag real-comparator 1 2 3) (bag real-comparator 1 6))
  ⇒ #f

(bag<=? bag1 bag2 …) → boolean

Returns true if each bagi other than the last is included in bagi+1, and #f otherwise. A bag A is included in a bag B if and only if

(<= (bag-element-count A x)
    (bag-element-count B x))

is true for every element x of A.

Examples
(bag<=? (bag real-comparator 1 2 3 3) (bag real-comparator 2 3 1 3))
  ⇒ #t

(bag<=? (bag real-comparator 1 3 2) (bag real-comparator 1 3 3 2))
  ⇒ #t

(bag<=? (bag real-comparator 1 3 2) (bag real-comparator 1 3 3 2 4))
  ⇒ #t

(bag<=? (bag real-comparator 1 3 3 2) (bag real-comparator 1 3 2 4))
  ⇒ #f

(bag<=? (bag real-comparator 1 2 3) (bag real-comparator 1 6))
  ⇒ #f

(bag<? bag1 bag2 …) → boolean

With two arguments, returns #t if bag1 is included in bag2 (in the sense of bag<=?) and bag1 has fewer elements than bag2. With a single argument, or with more than two arguments, bag<? returns #t if each bagi other than the last satisfies this relation with bagi+1. Otherwise, it returns #f.

Examples
(bag<? (bag real-comparator 1 2 3) (bag real-comparator 2 3 1 3))
  ⇒ #t

(bag<? (bag real-comparator 1 3 3 2) (bag real-comparator 1 3 3 2))
  ⇒ #f

(bag<? (bag real-comparator 1 3 2) (bag real-comparator 1 3 3 2 4))
  ⇒ #t

(bag<? (bag real-comparator 1 2 3) (bag real-comparator 1 3 6))
  ⇒ #f

(bag>=? bag1 bag2 …) → boolean

Returns true if each bagi other than the last includes bagi+1, and #f otherwise. A bag A is included in a bag B if and only if (bag<=? B A) is true.

Examples
(bag>=? (bag real-comparator 1 2 3 3) (bag real-comparator 2 3 1 3))
  ⇒ #t

(bag>=? (bag real-comparator 1 3 3 2) (bag real-comparator 1 3 2))
  ⇒ #t

(bag>=? (bag real-comparator 1 3 3 2 4) (bag real-comparator 1 3 2))
  ⇒ #t

(bag>=? (bag real-comparator 1 3 2) (bag real-comparator 1 2 3 3))
  ⇒ #f

(bag>=? (bag real-comparator 1 6) (bag real-comparator 1 2 3))
  ⇒ #f

(bag>? bag1 bag2 …) → boolean

With two arguments, returns #t if bag1 includes bag2 (in the sense of bag>=?) and bag1 has more elements than bag2. With a single argument, or with more than two arguments, bag>? returns #t if each bagi other than the last satisfies this relation with bagi+1. Otherwise, it returns #f.

Examples
(bag>? (bag real-comparator 1 2 3 3) (bag real-comparator 2 1 3))
  ⇒ #t

(bag>? (bag real-comparator 1 3 3 2) (bag real-comparator 1 3 3 2))
  ⇒ #f

(bag>? (bag real-comparator 1 3 3 2 4) (bag real-comparator 1 3 2))
  ⇒ #t

(bag>? (bag real-comparator 1 3 3 2) (bag real-comparator 1 2 3))
  ⇒ #t

(bag>? (bag real-comparator 1 2 3) (bag real-comparator 1 6))
  ⇒ #f

Set theory operations

(bag-union bag1 bag2 …) → bag

Returns a newly allocated bag that is the union of the bags. The number of equal elements in the result is equal to the largest number of equal elements in any of the bags.

Examples
(bag->list
 (bag-union (bag real-comparator 1 3 3)
            (bag real-comparator 2 2 4 3))
  ⇒ (1 2 2 3 3 4)  ; order unspecified

(bag->list
 (bag-union (bag real-comparator 1 3 3)
            (bag real-comparator 2 2 4 3)
            (bag real-comparator 1 1 1 4)))
  ⇒ (1 1 1 2 2 3 3 4)  ; order unspecified

(bag-intersection bag1 bag2 …) → bag

Returns a newly allocated bag that is the intersection of the bags. The number of equal elements in the result is equal to the smallest number of equal elements in any of the bags.

Examples
(bag->list
 (bag-intersection (bag real-comparator 1 2 3 3)
                   (bag real-comparator 2 2 4 3 3)))
  ⇒ (2 3 3)  ; order unspecified

(bag->list
 (bag-intersection (bag real-comparator 1 2 3 3)
                   (bag real-comparator 2 2 4 3 3)
                   (bag real-comparator 3 4 5)))
  ⇒ (3)

(bag-difference bag1 bag2 …) → bag

Returns a newly allocated bag that is the asymmetric difference of the bags. The number of equal elements in the result is equal to the number of equal elements in bag1, minus the number of equal elements in the other bags, but not less than zero.

Examples
(bag->list
 (bag-difference (bag real-comparator 1 2 3 3 3)
                 (bag real-comparator 2 2 4 3)))
  ⇒ (1 3 3)  ; order unspecified

(bag->list
 (bag-difference (bag real-comparator 1 2 3 3 3)
                 (bag real-comparator 2 2 4 3)
                 (bag real-comparator 3 3)))
  ⇒ (1)

(bag-xor bag1 bag2 …) → bag

Returns a newly allocated bag that is the symmetric difference of the bags. The number of equal elements in the result is equal to the absolute value of the difference between the number of equal elements in bag1 and bag2.

Example
(bag->list
 (bag-xor (bag real-comparator 1 1 2 3 3 3)
          (bag real-comparator 2 2 3 3)))
  ⇒ (1 1 2 3)  ; order unspecified

(bag-union! bag1 bag2 …) → bag

(bag-intersection! bag1 bag2 …) → bag

(bag-difference! bag1 bag2 …) → bag

(bag-xor! bag1 bag2) → bag

These are linear-update variants of the set-theoretical procedures given above. They are allowed, but not required, to mutate bag1 to produce their results.

Comparator

bag-comparator

SRFI 128 comparator used to compare bags, which also allows sets of bags, etc. to be constructed. Bag-comparator is required to provide at least type-test and equality functions. It is an error to compare bags with different comparators (in the sense of eqv?).

Additional bag procedures

(bag-sum bag1 bag2 …) → bag

The bag arguments must all use the same comparator (in the sense of eqv?).

Returns a newly allocated bag using the same comparator as the bags and containing all the unique elements in all the bags, such that the count of each unique element in the result is equal to the sum of the counts of that element in the arguments.

Example
(bag->list
 (bag-sum (bag real-comparator 1 2 3)
          (bag real-comparator 2 3 4)
          (bag real-comparator 3 4 5))
  ⇒ (1 2 2 3 3 3 4 4 5)  ; order unspecified

(bag-sum! bag1 bag2 … ) → bag

A linear-update variant of bag-sum which is allowed, but not required, to mutate bag1 to produce its result.

(bag-product k bag) → bag

It is an error if k is not a non-negative exact integer.

Returns a newly allocated bag using the same comparator as bag and containing all the unique elements in bag, where the count of each unique element in the resulting bag is equal to the count of that element in bag multiplied by k.

Example
(bag->list
 (bag-product 3 (bag real-comparator 1 2))
  ⇒ (1 1 1 2 2 2)  ; order unspecified

(bag-product! k bag) → bag

A linear-update variant of bag-product which is allowed, but not required, to mutate bag to produce its result.

(bag-unique-size bag) → exact-integer

Returns the number of unique elements of bag.

Example
(bag-unique-size (bag real-comparator 1 1 2 3 3 3)) ⇒ 3

(bag-element-count bag element) → exact-integer

Returns an exact integer representing the number of times that element appears in bag.

Examples
(bag-element-count (bag real-comparator 1 1 2) 1) ⇒ 2

(bag-element-count (bag real-comparator 1 1 2) 3) ⇒ 0

(bag-for-each-unique proc bag) → unspecified

Proc must be a procedure accepting two arguments, an element of bag and an exact integer.

Applies proc to each unique element of bag in arbitrary order, passing the element as the first argument and the number of times it occurs in bag as the second, and discarding the returned values. Returns an unspecified result.

Example
(let ((elts '()))
  (bag-for-each-unique (lambda (e c)
                         (set! elts (cons (cons e c) elts)))
                       (bag real-comparator 1 1 2 3 3 3))
  elts)
  ⇒ ((1 . 2) (2 . 1) (3 . 3))  ; order unspecified

(bag-fold-unique proc nil bag) → obj

Proc must be a procedure accepting three arguments and returning a single value. Nil may be any Scheme object.

Invokes proc on each unique element of bag in arbitrary order, passing the number of occurrences (an exact integer) as the second argument and the result of the previous invocation as the third argument. For the first invocation, nil is used as the third argument. Returns the result of the last invocation.

Example
(bag-fold-unique (lambda (e c elts)
                   (cons (cons e c elts)))
                 '()
                 (bag real-comparator 1 1 2 3 3 3))
  ⇒ ((1 . 2) (2 . 1) (3 . 3))  ; order unspecified

(bag-increment bag element k) → bag

It is an error if k is not a non-negative exact integer.

Returns a newly allocated bag using the same comparator as bag and containing the same elements, but with k additional copies of element.

Example
(bag->list
 (bag-increment (bag real-comparator 1 1 2 3) 2 3))
  ⇒ (1 1 2 2 2 2 3)  ; order unspecified

(bag-increment! bag element k) → bag

A linear-update variant of bag-increment which is allowed, but not required, to mutate bag to produce its result.

(bag-decrement bag element k) → bag

It is an error if k is not a non-negative exact integer.

Returns a newly allocated bag using the same comparator as bag and containing the same elements, but with k fewer copies of element (minimum zero).

Examples
(bag->list
 (bag-decrement (bag real-comparator 1 1 1 2 3) 1 2))
  ⇒ (1 2 3)  ; order unspecified

(bag->list
 (bag-decrement (bag real-comparator 1 2 3) 1 3))
  ⇒ (2 3)  ; order unspecified

(bag->list
 (bag-decrement (bag real-comparator 1 2 3) 4 10))
 ⇒  (1 2 3)  ; order unspecified

(bag-decrement! bag element k) → bag

A linear-update variant of bag-decrement which is allowed, but not required, to mutate bag to produce its result.

Implementation

A sample implementation in R7RS Scheme can be found in the SRFI repository. It implements sets and bags on top of the hashmaps of (srfi 146 hash). In addition to SRFI 146, the implementation requires SRFIs 1, 128, and 151. To run the included test suite you will also need SRFIs 64 and 132.

Acknowledgements

Thanks to John Cowan for creating SRFIs 113 and 153. His work provided the template for this SRFI.

Thanks to those who provided feedback on this SRFI via the SRFI mailing list or the #scheme IRC channel, including:

Of course, none of the above should be taken to mean that any of these people endorse the SRFI’s final draft.

References

S. Bradner, Key words for use in RFCs to Indicate Requirement Levels (RFC 2119), 1997. https://datatracker.ietf.org/doc/html/rfc2119

John Cowan. SRFI 113: Sets and bags. https://srfi.schemers.org/srfi-113/, 2014.

John Cowan. SRFI 128: Comparators (reduced). https://srfi.schemers.org/srfi-128/, 2016.

Arthur A. Gleckler and Marc Nieper-Wißkirchen, SRFI 146: Mappings. https://srfi.schemers.org/srfi-146/, 2018.

John Cowan. SRFI 153: Ordered sets. https://srfi.schemers.org/srfi-153/, 2023.

John Cowan. SRFI 162: Comparators sublibrary. https://srfi.schemers.org/srfi-162/, 2019.

Alex Shinn, John Cowan, & Arthur A. Gleckler, eds., Revised7 Report on the Algorithmic Language Scheme (R7RS Small), 2013. https://small.r7rs.org

© 2026 Wolfgang Corcoran-Mathe and John Cowan.

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 (including the next paragraph) 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.


Editor: Arthur A. Gleckler