Repository navigation
Category OrderedMap
Home › All Categories › OrderedMap
An OrderedMap is a map that keeps its keys in sorted order. Like any map it stores pairs -- a key and the value that goes with it -- each key appears at most once, and you find a value by its key. The difference shows when you walk through it: an OrderedMap always hands its entries back from the smallest key to the largest, whatever order they were put in.
Think of a printed dictionary: the words are kept in alphabetical order, so you can read it from A to Z, turn to the first or the last word, or find the words just before and just after one that is not in it.
Those last questions are what an OrderedMap can answer and an ordinary Map cannot: the smallest key (TryFirst), the largest (TryLast), the largest key at or below a given one (TryFloor) and the smallest at or above it (TryCeiling). A key only has to support <.
OrderedSet is the same idea for a set: values with no duplicates, kept in sorted order. Both are described on this page.
Keeping things sorted has a cost: each operation takes a little longer as the container grows, where a Map's or a Set's stays about the same. So use an ordinary Map or Set unless you need the order -- see Which One To Use below.
The example below stores ages by name and prints them alphabetically, then records a number in an OrderedSet:
#import "std/containers"
using containers
dim ages as OrderedMap( of ustring, int32 )
ages.Put( "bob", 41 )
ages.Put( "ada", 36 )
for each kv in ages
print kv.key, kv.value ' "ada" then "bob", always
next
dim seen as OrderedSet( of int32 )
if seen.Add( 42 ) then print "first time"The always-sorted siblings of containers.Map and containers.Set. Same operations, plus the four an ordered container can answer and a hash one cannot, and FOR EACH walks them in ascending key order -- which is the entire reason to reach for them.
containers.Map and containers.Set unless you need the ORDER.
Map / Set avg O(1) order unspecified and unstable
OrderedMap / OrderedSet O(log n) ascending, alwaysA hash map is faster on every single operation, allocates once instead of once per entry, and does not chase pointers on lookup. These are the right choice when you need keys in order, the smallest or largest key, or the nearest key either side of a probe -- not as a general-purpose map.
There is one case where they win outright: a key type that has '<' but no hash.
Map needs K to extend containers.Hashable (or be a
built-in type), and '=', and equal keys must
hash equal
OrderedMap needs '<'. Nothing else.A type with an ordering and no hash cannot be a Map key at all.
OrderedMap( of TK, TV ):
Count( ) as int32 O(1)
IsEmpty( ) as boolean O(1)
Put( k, v ) as boolean O(log n) TRUE if k was new
Add( k, v ) as boolean O(log n) FALSE if k existed
Contains( k ) as boolean O(log n)
TryGet( k, byref outv ) as boolean O(log n)
Remove( k ) as boolean O(log n) TRUE if removed
m[ k ] byref as TV O(log n) INSERTS on a miss
Clear( ) O(n)
TryFirst( byref outk, byref outv ) O(log n) smallest key
TryLast( byref outk, byref outv ) O(log n) largest key
TryFloor( k, byref outk ) as boolean O(log n) largest key <= k
TryCeiling( k, byref outk ) as boolean O(log n) smallest key >= k
Keys( ) as Array( of TK ) O(n) sorted snapshot
Values( ) as Array( of TV ) O(n) in key order
GetIterator( ) O(log n)
Pairs( ) O(1) the tuple view
BlackHeight( ) as int32 O(n) -1 if corruptOrderedSet( of T ):
Count( ) / IsEmpty( ) O(1)
Add( v ) as boolean O(log n) FALSE if present
Contains( v ) as boolean O(log n)
Remove( v ) as boolean O(log n)
Clear( ) O(n)
UnionWith( other ) O(m log n)
IntersectWith( other ) O(n log m)
ExceptWith( other ) O(m log n)
TryFirst( byref outv ) / TryLast( byref outv )
TryFloor( v, byref outv ) / TryCeiling( v, byref outv )
Items( ) as Array( of T ) O(n) SORTED snapshot
GetIterator( ) O(log n)
BlackHeight( ) as int32 O(n)NO Reserve AND NO Capacity on either, unlike Map and Set: a tree allocates exactly one node per entry when that entry arrives, so there is no table to size in advance and nothing to tune.
Exactly as Map, and for the same reasons:
m[ k ] INSERTS a default-constructed TV if k is absent.
m.TryGet does not.
m.Contains does not.'m[ k ] += 1' just working is why the indexer inserts, and 'if m[ k ] = 0' silently growing the map is the price.
'<' on the key, and nothing else. No HashOf, and no '=' either:
Two keys are THE SAME KEY when neither is less than the other.That is what std::map means by an equivalence, and it is why Put( ) can detect an overwrite without ever comparing for equality. A '<' that is not a strict weak ordering -- one where 'a < a' is true, or where a < b and b < c does not give a < c -- produces a tree that is merely wrong, not one that crashes.
The requirement lands on the TYPE, not on a call:
dim m as OrderedMap( of NoLess, int32 ) ' error 20 here, at the DIMThat is deliberate and is the difference from Array's Sort, which is a free procedure so that an Array of an unorderable T still works. An ordered map has no meaning without an ordering, so there is nothing to defer. src/tests/generics/fail-container-noorder.bas pins the diagnostic.
A red-black tree: a binary search tree in which every node is red or black, the root and every null child count as black, a red node has no red child, and every path from a node down to its descendant nulls holds the same number of black nodes. Those invariants bound the longest root-to-leaf path at twice the shortest, which is what makes the height O(log n) and every operation with it.
Chosen over an AVL tree because the rebalancing is cheaper on write -- at most two rotations for an insert and three for a removal, against AVL's O(log n) -- for a tree that is at most 2x rather than 1.44x the ideal height. A workload that would prefer the AVL trade is better served by Map anyway.
NODES CARRY PARENT POINTERS. That is what makes the iterator a plain successor walk with no stack of its own, so an iterator is one pointer and FOR EACH allocates nothing. It also keeps the removal rebalance readable.
Insertion colours the new node RED, which cannot break the equal-black-count invariant, only the no-red-child one -- which is why the insert fixup has just three cases and their mirrors. Removal is the harder half: the fixup has four cases per side and runs only when the node that left was black, because that is what changes a black count.
Like LinkedList and unlike the array-backed containers, these hold RAW NODE POINTERS, so the destructor, the copy constructor and 'operator let' are all written out.
Copy and assignment are DEEP and rebuild the whole tree, node for node and colour for colour -- so a copy is STRUCTURALLY IDENTICAL to its original, not merely equal to it. Rebuilding by walking in order and re-inserting would give a legal tree with different shape. Pass them BYREF where the cost matters.
An iterator holds a node pointer, so:
- INSERTING does not invalidate any iterator. It may rotate nodes into new
positions, but every node keeps its identity and the walk still reaches all
of them. Map cannot say this: any rehash invalidates everything.- REMOVING a key invalidates an iterator standing on THAT key, and nothing
else.Do not write through OrderedSet's iterator. Value( ) returns BYREF, matching Set, so 'for each byref x in s' compiles -- and assigning to x changes the key the tree is sorted by without moving the node. Remove and re-Add instead. The same hazard exists for Set, where it breaks the hash.
generics/container-orderedmap.bas and container-orderedset.bas, vendored in tiko-lang at tests/vendor/fbc-tests/generics/.
Every declared member, every state, the indexer's insert-on-miss, deep copy and assignment independence in both directions, self-assignment, destructor balance with a counting type, four key types including one with '<' and no hash, both FOR EACH binding forms and the tuple destructuring view, and -- for the set -- all three algebra operations including every self-argument case.
Two things those suites do that a hash container's cannot:
- THE INVARIANTS ARE ASSERTED, via BlackHeight( ), after every destructive
phase. A tree that has lost its balance still answers every lookup
correctly and still iterates in order; it has merely degraded to a linked
list, and no functional test can see that. BlackHeight( ) returns -1 on a
red-red violation, on unequal black counts, or on a red root, and all three
detections were checked against hand-corrupted trees before being relied on.- THE BALANCE CLAIM IS A TESTED CLAIM. 1,000 keys inserted in ascending order
-- the input a naive BST turns into a list -- must report a black height of
at most 20. A degenerate tree would report about 1,000.The removal rebalance's four cases and their mirrors need specific tree shapes that no hand-written case reliably produces, so both suites end in a DIFFERENTIAL STRESS: 120 rounds x 1,500 random operations mirrored into Map or Set, with every answer compared, the invariants checked each round, and Floor/Ceiling cross-checked against a linear scan.
Under tiko: both files build and run with exit 0 and print container-orderedmap: ok and container-orderedset: ok -- invariant checks and differential stress included.
See also: Map (containers.Map( of TK, TV )), Set (containers.Set( of T )), Sorting (sorting an Array or a LinkedList instead).
Every entry, grouped by task. Collections lists them beside the rest of the area.
| Function | What it does | From |
|---|---|---|
| OrderedMap | Holds key and value pairs kept sorted by key. | std/containers |
| Function | What it does | From |
|---|---|---|
| OrderedMap.Put | Stores a value under a key, replacing any old one; returns true if the key was new. | std/containers |
| OrderedMap.Add | Adds a key and value if the key is new; an existing value is left alone. | std/containers |
| OrderedMap.Remove | Removes a key and its value; returns true if the key was there. | std/containers |
| OrderedMap.Clear | Removes every entry. | std/containers |
| Function | What it does | From |
|---|---|---|
| OrderedMap.TryGet | Copies the value for a key into a variable; returns false, adding nothing, if absent. | std/containers |
| OrderedMap.Contains | Returns true if the key is present, without adding it. | std/containers |
| OrderedMap.Count | Returns the number of entries; reading an absent key with m[ k ] adds one. | std/containers |
| OrderedMap.IsEmpty | Returns true if the map has no entries. | std/containers |
| Function | What it does | From |
|---|---|---|
| OrderedMap.Keys | Returns a copy of all the keys as an Array, already sorted. | std/containers |
| OrderedMap.Values | Returns a copy of all the values as an Array, in the order of their keys. | std/containers |
| OrderedMap.Pairs | Returns a view that lets for each (k, v) walk keys and values in key order. | std/containers |
| Function | What it does | From |
|---|---|---|
| OrderedMap.TryFirst | Copies the smallest key and its value into variables; returns false if the map is empty. | std/containers |
| OrderedMap.TryLast | Copies the largest key and its value into variables; returns false if the map is empty. | std/containers |
| OrderedMap.TryFloor | Finds the largest key at or below a given one; returns false if there is none. | std/containers |
| OrderedMap.TryCeiling | Finds the smallest key at or above a given one; returns false if there is none. | std/containers |
| Function | What it does | From |
|---|---|---|
| OrderedMap.GetIterator | Returns an iterator over the entries in ascending key order. | std/containers |
| OrderedMapIterator | Holds a position in an OrderedMap, stepping in ascending key order. | std/containers |
| OrderedMapPairView | Holds the view that OrderedMap.Pairs returns, for for each (k, v) to walk. | std/containers |
| OrderedMapPairIter | Holds a position in OrderedMap.Pairs, giving key and value as a tuple in key order. | std/containers |
| Function | What it does | From |
|---|---|---|
| OrderedMap.BlackHeight | Returns the tree's black height, or -1 if its balancing rules are broken. | std/containers |
tiko-lang · Home · Feature Index · Keyword Index · License
FreeBASIC manual pages reproduced under the GNU FDL 1.3.
- Generics
- Interfaces
- For Each
- Iterators
- Tuples
- Array Results
- Defer
- Lambdas
- Block Strings
- Partial Types
- String Slices
- Embed
- Typed Variadics
- C Header Import
- Foreign Library Linking
- UString
- Fixed-Width Types
- Nullptr
- String Library
- Format
- Array
- Map
- Set
- LinkedList
- OrderedMap
- OrderedSet
- Sorting
- Optional
- Result
- Smart Pointers
- Date and Time (Chrono)
- Filesystem
- File Streams
- Config (INI and TOML)
- Codec
- Hash
- UUID
- OS
- Process
- Net
- HTTP and TLS
- Math Library
- Finance
- Logging
- Terminal TUI Library
- PsControls
- tkControls
- sys/win
- Goroutines and Channels
- LLVM Backend
- tiko build
- TIKO_COMPILER
- Variable Declarations
- Standard Data Types
- User Defined Types
- Procedures
- Operators
- Control Flow Statements
- Converting Data Types
- Array Functions
- String Functions
- Mathematical Functions
- Bit Manipulation
- Date and Time Functions
- File I/O Functions
- Console Functions
- User Input (Runtime Library)
- User Input Functions (Graphics Library)
- Screen Functions
- 2D Drawing Functions
- Graphics Library Reference
- Graphics Keyword List
- Memory Functions
- Threading Support Functions
- Error Handling Functions
- Operating System Functions
- Preprocessor
- Intrinsic Defines
- Miscellaneous Keywords
- Modularizing