Simple, Deterministic Dictionaries

This package defines immutable and mutable deterministic dictionaries (or ddicts, for
short). A ddict is a
dictionary (i.e. it implements the generic interface
gen:dict) whose API mimics that of a
hash table but which also guarantees LIFO ordering when iterating
over the elements of the dictionary.

Caveats concerning
concurrent modification: A mutable ddict can be
manipulated with ddict-set!,
ddict-remove!, ddict-ref!, and
ddict-update! concurrently by multiple threads;
updates to internal state are performed with a check-and-set
operation which protects from invalid internal states.
However, the following caveats apply:

Creates a ddict with each key mapped to the
immediately following val. Each key must
have a val, so the total number of arguments must
be even. Each constructor also specifies how keys are
compared (e.g. ddict compares keys with equal?,
ddicteqv compares keys with eqv?, etc).

The key to val mappings are added to the table in the order they appear in
the argument list, so later mappings can hide earlier ones if the keys are equal.

Creates a ddict that is initialized with the contents of assocs. In each element of
assocs, the car is a key, and the cdr is the corresponding value. The mappings
are added to the table in the order they appear in the argument list, so later mappings can hide earlier
ones if the keys are equivalent w.r.t. the table’s key comparison function.

4Additional Operations

Functionally extends dd by mapping each key to the following val, overwriting
any existing mapping for each key, and returning the extended ddict. Mappings are added to
the table in the order they appear in the argument list, so later mappings can hide earlier ones if the
keys are equivalent w.r.t. the table’s key comparison function.

Extends dd by mapping each key to the following val, overwriting
any existing mapping for each key, and returning the extended ddict. Mappings are added to
the table in the order they appear in the argument list, so later mappings can hide earlier ones if the
keys are equivalent w.r.t. the table’s key comparison function.

Returns the value for key in dd. If no value is
found for key, then to-set determines the result as
in ddict-ref (i.e., it is either a thunk that computes a value
or a plain value), and this result is stored in dd for the
key.

Like for/hash, but producing a ddict with the
respective mutability and key comparison function.

6Performance and Memory Usage

Performance. Immutable and mutable ddicts
internally use Racket’s immutable hash data
structure along with a list of keys in order to
provide hash-like performance and a deterministic
iteration order. ddict operations obviously have
overhead which the native hash operations do not, but as a
dictionary increases in size the overhead should become less
noticeable (i.e. the overhead is constant for the atomic
ddict operations so the asymptotic complexity is
unaffected). To determine if this overhead is acceptable you
should of course perform your own application-specific
benchmarks.

Memory Usage. In order to keep ddict
operations such as ddict-remove efficient (i.e.
non-linear), we do not immediately remove elements from the
internal key list. Instead, a certain amount of
“fragmentation” in the key list is tolerated, but once it
passes a predetermined threshold (when the number of removed
key slots exceeds the number of active keys), the list is
then “defragmented”. To prevent this fragmentation from
causing unexpected memory leaks, each key in the key list is
stored in a weak-box so its presence in the key list
after removal does not prevent garbage collection that would
otherwise occur.

The hope is that these implementation details are mostly
unobservable to ddict users since their costs will be
amortized.

If a user is concerned and wants more fine grained control
over the presence of internal fragmentation (e.g. if
removals are performed early on in computation then never
again) the following functions report on the presence
of fragmentation and allow a user to remove any: