Repository navigation
Category OrderedSet
Home › All Categories › OrderedSet
An OrderedSet is a set that keeps its values in sorted order. Like any set it holds each value at most once -- adding one that is already there changes nothing -- and you can add a value, take one out, and ask whether a value is in it. The difference shows when you walk through it: an OrderedSet always hands its values back from the smallest to the largest, whatever order they were added in.
Think of the index at the back of a book: every term appears once, in alphabetical order, so you can read it from A to Z, see the first and the last term, or find the terms just before and just after one that is not listed.
Those last questions are what an OrderedSet can answer and an ordinary Set cannot: the smallest value (TryFirst), the largest (TryLast), the largest value at or below a given one (TryFloor) and the smallest at or above it (TryCeiling). A value only has to support <.
OrderedMap is the same idea for a map: key and value pairs, kept in key 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 Set's or a Map's stays about the same. So use an ordinary Set or Map unless you need the order -- see Which One To Use below.
The example below stores ages by name in an OrderedMap 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. 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 |
|---|---|---|
| OrderedSet | Holds each value at most once, kept in sorted order. | std/containers |
| Function | What it does | From |
|---|---|---|
| OrderedSet.Add | Adds a value; returns false if it was already there. | std/containers |
| OrderedSet.Remove | Removes a value; returns true if it was there. | std/containers |
| OrderedSet.Contains | Returns true if the value is in the set. | std/containers |
| OrderedSet.Count | Returns the number of values in the set. | std/containers |
| OrderedSet.IsEmpty | Returns true if the set has no values. | std/containers |
| OrderedSet.Clear | Removes every value. | std/containers |
| OrderedSet.Items | Returns a copy of all the values as an Array, already sorted. | std/containers |
| Function | What it does | From |
|---|---|---|
| OrderedSet.UnionWith | Adds every value of another set to this one; the other set is unchanged. | std/containers |
| OrderedSet.IntersectWith | Keeps only the values that are also in another set. | std/containers |
| OrderedSet.ExceptWith | Removes every value that is also in another set. | std/containers |
| Function | What it does | From |
|---|---|---|
| OrderedSet.TryFirst | Copies the smallest value into a variable; returns false if the set is empty. | std/containers |
| OrderedSet.TryLast | Copies the largest value into a variable; returns false if the set is empty. | std/containers |
| OrderedSet.TryFloor | Finds the largest value at or below a given one; returns false if there is none. | std/containers |
| OrderedSet.TryCeiling | Finds the smallest value at or above a given one; returns false if there is none. | std/containers |
| Function | What it does | From |
|---|---|---|
| OrderedSet.GetIterator | Returns an iterator over the values in ascending order. | std/containers |
| OrderedSetIterator | Holds a position in an OrderedSet, stepping in ascending order. | std/containers |
| Function | What it does | From |
|---|---|---|
| OrderedSet.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