Spec chapter 14
Strings and Collections
Strings
- UTF-8, immutable, refcounted. A string is always valid UTF-8, of at most 2147483647
bytes, as many as an
Intcounts; one past that panics, at the+, interpolated string, or std call that would make it: “a String holds at most 2147483647 bytes”. +only joinsStringtoString. Everything else uses interpolation.- A String’s Methods are std.text’s (see stdlib.md), by byte position: its
Lengthis its UTF-8’s bytes, and a position is a byte offset, whichFindgives andSlicetakes. A position inside a character panics.CharCountandGraphemeCountcount code points and what people see as one character.
Print("HP: {hp}/{MAX_HP}") // interpolation; {{ for a literal brace
Print("a\tb\nc \\ \" \0 \x41 \u{1F600}") // escapes (see 01-lexical)
Local path = r"C:\raw\no\escapes" // raw string
Local text = """
multi-line string;
the closing quotes' indentation is trimmed
"""| Form | Escapes | Interpolation | Notes |
|---|---|---|---|
"..." | yes | yes | single line; a lone } is an error (write }}) |
r"..." | no | no | single line; cannot contain " |
"""...""" | yes | yes | multi-line |
r"""...""" | no | no | multi-line; may contain " |
Interpolation
{expr} accepts any expression, including string literals: "{name ?? "nobody"}". Numbers
print in the shortest form that reads back as the same value (0.1, not 0.100000001), the same
on every platform: an exponent as 1e21 or 1.5e-7, and NaN, Inf, and -Inf. A Bool
prints as True or False. A tuple prints as (1, a), and an array as [1, 2, 3]. A Struct,
Class, or Interface’s object that implements Printable prints what its ToString gives. A
Task, Thread, Channel, Scheduler, Atomic, or Mutex has no text, so it can’t be shown, though the
debug form of what holds it can; a Task’s result can, through Await or Wait(). In a
single-line string, an interpolation must end on the same line.
Format specs
Print("{price:.2}") // 3.14
Print("{name:<10}|{hp:>5}|{title:*^9}") // Ann | 42|**King***
Print("{n:+,} {mask:#010b} {color:06X}") // +1,234 0b00001010 00FF80
Print("{ratio:.1%} {big:e} {tiny:.3e}") // 45.0% 6.02e23 1.500e-7{expr:spec} writes a value by a format spec, in Python’s and Rust’s mini-language:
[[fill]align][sign][#][0][width][grouping][.precision][kind]| Part | Meaning |
|---|---|
| fill | any one character but { and }, before an align; a space without one |
| align | < left, > right, ^ centered; numbers go right without one, the rest left |
| sign | + on every number, - on negatives only (the default), a space before the rest |
# | 0x, 0o, or 0b before x, X, o, or b’s digits; with ?, many lines |
0 | pads a number with zeros after its sign and prefix; not with an align |
| width | the fewest characters (code points) the text takes, padded to it; at most 999 |
| grouping | , between each 3 digits of a decimal; _ each 3, or each 4 for x, o, b |
| precision | .N: a Float’s digits after the point, or a String’s most characters; at most 999 |
| kind | integers: d (the default), x, X (upper digits), o, b; Floats: f, e, E, %; any value: ? |
.2alone isfwith 2 decimals.f,e,E, and%without a precision write the shortest digits that read back, andfand%write them without an exponent, so a Float of 1e23 is100000000000000000000000. So does a spec with,or0and no kind or precision.%is the value times 100, which moves the point two digits, exactly, then%, andInf%andNaN%keep it.- A Bool, Char, Enum, String, Printable value, tuple, or array takes a fill, an alignment, and a width, and a String a precision too. A Char is its character, never its code.
- An empty spec, as in
"{x:}", is none. - The compiler checks the spec against its value’s type: one that doesn’t fit is an error at the spec (“Int32 doesn’t take a precision in its format spec”). In generic code, a T takes what its constraints say it does, any spec for a Formattable T, and each instance whose type doesn’t take the spec panics at the spec.
- In an interpolation, a
:outside(,[, and{that has no space or tab after it starts the spec, which runs to the}; a spec can’t hold a brace. A ternary’s:and a function type’s): Inthave a space after them, so"{a ? b : c}"is code, and"{(a ? b : c):>8}"pads it.
Formattable
Struct Money Implements Formattable
Field Cents: Int64
Method ToString(): String => "${Cents / 100}.{Cents % 100:02}"
Method Format(spec: String): String?
If spec == "cents" Then Return "{Cents} cents"
Return Null
End Method
End Struct
Print("{price} {price:cents}") // $3.50 350 centsInterface Formattable Extends Printable(std.core) hasMethod Format(spec: String): String?, which gives Null for a spec it doesn’t take. The numbers, Bool, Char, String, and Enums implement it, by the specs above."{x:spec}"on a Formattable type of the program’s own calls its Format with the spec’s text, and panics at the spec when that gives Null. Its debug form,?, is still the compiler’s.
Format
Local line = Try Format(r"{} scored {1:>6.1} ({0})", name, score)Format(template: String, values: Formattable...): String Throws FormatError(std.core) fills a template the program has only when it runs.{}is the value after the last{}’s,{0}the first value, and either can take a spec after a:;{{and}}are braces. A field without a spec, or with an empty one, shows its value as"{x}"does. A field that asks for the debug form gets what the value’s own Format gives for that spec, as the built-in types’ do, padded as they pad, or else the value’s debug form as an interpolation writes it, padded. In source, a template is a raw string, whose braces aren’t interpolations.- It throws a FormatError:
BAD_TEMPLATEfor a template that isn’t one,MISSING_VALUEfor a field whose value isn’t given, andBAD_VALUEfor a spec its value doesn’t take.
Parsing
Local hp = Int.Parse("42") ?? 0
Local mask = UInt8.Parse("0xff", 16)
Local speed = Float.Parse("1.5e-7")
Local on = Bool.Parse("True")- Each integer type’s
Parse(text, base = 10), each Float type’sParse(text), andBool.Parse(text)give aT?: Null for text that isn’t one, or a number its type can’t hold. A Float too small for its type reads as the nearest one, 0, and-0as an unsigned 0. - An integer is a sign, then digits of the base, which
0x,0o, or0bcan start for bases 16, 8, and 2; a base outside 2 to 36 panics. A Float reads what interpolation writes,NaN,Inf, and-Inftoo, so every Float64’s text reads back as itself. A Bool isTrueorFalse. Parsing is invariant:.for decimals, and no grouping.
The debug form
Print("{save:?}") // Save { Name: "Ann", Scores: [10, 20], Best: 99, Rival: Null }
Print(Dump(save)) // the same, with each part on a line of its own"{x:?}"shows any value as code would write it, from reflection, the same on every platform: a number as its shortest text, a String in quotes with escapes, a Char as its literal, like'é'or'\n', an Enum as its member, a @Flags value’s bits that no member has after its members’ in hex, asWorld | 0x8, aT?as its value orNull, a tuple as(1, "a"), an array as[1, 2], a fixed array of several dimensions as one list, and a Struct or object asPoint { X: 1, Y: 2 }, with every Field, by its object’s own Class. A Weakshows what reading it gives, and a function value, Task, Thread, Channel, Scheduler, CString, or Ptr<T>its type’s name, and so does an Atomic or Mutex inside what holds it, which is the only place it’s shown. A value in std’s box for avalues: Formattable...parameter shows as itself.- An object that’s already being shown, in a cycle, shows as
Node { … }, and an array as[ … ], as a graph of Structs through their arrays can be. "{x:#?}"writes each part on a line of its own, indented four spaces, with a comma after it, andDump(x)(std.core) gives that text. Both take a fill, an alignment, and a width.- std’s collections show their elements, as
Stack<Int32> [1, 2], and a Map its entries, asMap<String, Int32> {"a": 1}. That’s std.core’s@DebugElements, on a Struct or Class that implementsIterable<T>, and@DebugEntries, on one whose T is a (key, value) tuple, which a type of the program’s own can have too. A Grid and a Grid3 show their Fields, their cells and their sizes, which their elements alone would leave out.
Multi-line strings
- The opening
"""ends its line (only spaces or tabs may follow it); the closing"""stands on its own line. - The closing
"""’s indentation is removed from every line. A non-blank line that doesn’t start with exactly that indentation is an error. A line that starts inside an interpolation isn’t string text, so it isn’t checked. - The line breaks right after the opening
"""and right before the closing"""are not part of the string. Line breaks inside the string are always\n, whatever the source file uses.
Indexing
There is no s[i] integer indexing (it would be wrong or slow with UTF-8).
For c In s.Chars ... End For // Unicode code points
For b In s.Bytes ... End For // raw bytess.Charsis a newChar[]of the String’s code points, ands.Bytesa newByte[]of its UTF-8 bytes. AChar’s Methods, likeIsLetter()andToUpper(), are std.text’s too.
Arrays
Local nums: Int[] = [1, 2, 3] // dynamic array (reference type)
nums.Add(4)
Local grid: Float[64, 64] // fixed-size, multi-dimensional (value type)
Local mid = nums[1..3] // slice: a new Int[] of elements 1 and 2
Local copy = nums.Clone()| Form | Semantics |
|---|---|
T[] | dynamic, growable, reference |
T[N], T[W, H] | fixed size, value (stored inline) |
arr[a..b] | slice: a new T[] of the elements from a up to b |
Out-of-bounds access panics.
- An array literal takes the type it’s used as, like
Local a: Long[] = [1, 2], or else its elements’ common type, with literals taking it:[1, 2.5]is aFloat[]. Where anIterable<T>of a known T is expected, it’s aT[], so[1, 2]and[]go to aSet<Long>’s AddAll.[]needs a type from where it’s used. AT[N]can come from a literal of N elements; aT[W, H]can’t. - A
T[]hasCount(anInt) and the MethodsAdd(value),Insert(index, value),RemoveAt(index),Clear(),Contains(value), andClone().Clonecopies the elements, not what they refer to.Containscompares with==. A fixed array hasCountonly. - A
T[]holds at most 2147483647 elements, as many as itsCountcounts, and adding one more panics: “an array holds at most 2147483647 elements”. - A
T[]’sCapacityis how many elements it has room for before it needs more memory;Reserve(capacity)makes room for that many in all, andTrimExcess()gives back what’s beyond its Count. Neither changes the elements. A negative capacity panics. - std/collections/array.fb gives a
T[]the rest of the collections’ Methods (below), and makes it anIterable<T>, whoseIterate()gives its elements, so it goes where anIterable<T>is expected, as it does where aPrintableis, when its elements are. - An index can be any integer. A
T[W, H]takes one per dimension,grid[x, y], and stores its elements row by row. An index out of range panics: “index 5 is out of range for 3 elements”. A fixed array’s index known at compile time is checked then. - A
T[]is a reference, so its elements change through any reference to it. A fixed array is a value, copied on assignment, and starts with every element at zero; aLocalof one is ready when declared. - A
T[]has no zero. AGlobalof one needs a value, and a field needs a default or aNewthat assigns it.[]is a default anywhere: each use gets a new empty array. - A
T[]only holdsT: aBoss[]isn’t anEnemy[], since adding anEnemyto it would break it. ==compares two arrays’ elements, when they can be compared;RefEqualstells whether twoT[]s are the same array.- An element can’t be passed
ByRef, and aByRef Methodcan’t change aT[]’s element in place, since the elements move when the array grows. - A fixed array’s sizes are integers known at compile time: literals, or
Consts in code. - A slice,
arr[a..b], is a newT[]with copies of the elements fromaup tob, without the one atb.arr[a..]goes to the end, andarr[..b]starts at the start. It slices aT[]or a fixed array of one dimension. - A slice’s bounds are integers from 0 to the count, with the start no more than the end. Out of range, it panics: “slice 1..4 is out of range for 3 elements”. Known bounds are checked at compile time.
Collections (std.collections)
| Type | Notes |
|---|---|
T[] | dynamic array (the “list” type) |
Map<K, V> | hash map; literal {"a": 1, "b": 2} |
Set<T> | hash set |
Queue<T>, Stack<T>, Deque<T> | |
PriorityQueue<T> | |
Pool<T> | game-oriented object pool with stable handles; replaces what Blitz Type lists were used for |
All collections are reference types and share a common method vocabulary: Add,
Remove, Contains, Count, Clear, Sort, Filter, Map, Find, Clone. Each name
means the same thing wherever a collection has it.
Local hp: Map<String, Int> = {"grunt": 50, "boss": 500}
For name, value In hp ... End For
Local seen = Set<Int>()They’re written in FutureBlitz, in std/collections/. Each has a Count Property, Add,
Clear, Contains, and Clone, which copies the collection but not what its values refer to,
and goes in a For ... In. Taking a value from an empty Stack, Queue, Deque, or PriorityQueue
panics; Count says whether there is one. Adding or removing a value while a For ... In goes
over the collection panics at the loop’s next value, rather than skipping or repeating some,
except in a Pool; a Map’s key can get a new value meanwhile, and reading never changes a
collection.
| Type | Members besides those |
|---|---|
T[] | Remove(value), the first equal one, giving whether there was one; Sort() when T is Comparable, or Sort(before), in place and stable, SortUnstable() and SortUnstable(before), a heap sort that makes no array, and SortBy(key) and SortByDescending(key), whose ThenBy(key) and ThenByDescending(key) sort again where the keys so far tie; Filter(keep), Map(change), and Find(test) and FindLast(test), which give a T?; FindIndex, IndexOf, LastIndexOf, and BinarySearch, which give an Int?, and Any and All; First() and Last(), which give a T?, RemoveLast(), and TryRemoveLast(); AddAll(values), InsertAll(index, values), RemoveRange(start, count), RemoveAll(test), which gives how many went, Resize(count, fill), and Fill(value); Reverse() and Swap(a, b); Distinct(), Join(separator), Fold(start, combine), and Reduce(combine); View(start, finish) and AsReadOnly() |
Map<K, V> | Add(key, value) adds or replaces, TryAdd only adds, and AddAll(other) adds another’s; map[key] reads a value and map[key] = value adds or replaces one; Get(key) gives a V?, Get(key, fallback) a V, and GetOrAdd(key, make) adds what make gives when the key isn’t there, as the key’s value even if make added the key; Update(key, change) gives a key’s value what change gives for it, unless change removed the key; Remove(key) and RemoveWhere(test); Keys(); Values(); Filter(keep), Find(test), and Any(test) with a Func(K, V): Bool; MapValues(change); Reserve(count) |
Set<T> | Add(value) gives whether it’s new; Remove(value); Filter(keep); Find(test); Union, Intersect, Difference, and SymmetricDifference make a new Set, and AddAll, RemoveAll, and KeepOnly change it, from any Iterable<T>; IsSubsetOf, IsSupersetOf, Overlaps, and SetEquals; ToArray() |
Stack<T> | Push, Pop, and Peek, at the top, and TryPop and TryPeek, which give a T?; it goes from the top down, and so does ToArray() |
Queue<T> | Enqueue at the back, Dequeue and Peek at the front, and TryDequeue and TryPeek; ToArray() goes from the front |
Deque<T> | AddFirst, AddLast, RemoveFirst, RemoveLast, First, and Last, and the Try twins of the last four; Add is AddLast; deque[i] reads and sets the value i from the first; ToArray() goes from the first |
PriorityQueue<T> | Pop and Peek give the first: the smallest when T is Comparable, where a value that isn’t equal to itself, like NaN, panics where it’s added, or what PriorityQueue<T>(before) says comes first, and TryPop and TryPeek give a T?; Drain() takes every value off, in the order Pop would; it goes over its values in no particular order, and so does ToArray() |
Pool<T> | Add(value) gives a Handle<T>; Get(handle) gives a T?; Set(handle, value); Remove(handle); IterateHandles() gives each value with its Handle, For handle, value In pool.IterateHandles(); Capacity and Reserve(count) |
- What finds a value by comparing it, like an array’s
Remove(value),IndexOf, andLastIndexOf, and the collections’Contains(value), needs the values to beEquatable, as==in any generic code does (07-generics.md). A Struct is when it listsEquatable, and every Class is, comparing its objects themselves without an==of its own, soenemies.Remove(e)needs nothing more, but for one whose==has a Where line, as a Map’s, a Grid’s, and a LinkedList’s do, which is Equatable only where the line holds. Only aT[]’s built-inContainstakes any element that has==. - What gives a
T?for “none”, likeGet,Find, and an Iterator’sNext, gives aT??for a collection of aT?, so a storedNullisn’t “none”: a For … In over aStack<Int?>goes over its Nulls, andGetof aMap<String, Int?>tells a missing key from aNullvalue. - A Map or Set needs its keys to be
Hashable, as every Class is unless it has an==of its own without aHash, or one with a Where line, which holds it only where the line holds. Its entries are in the order they were added, and removing one leaves the rest in order. A Map or Set whose keys and values arePrintableis too:{a: 1, b: 2},{1, 2}. - Two Maps are
==when they have the same keys, each with an equal value, in any order, and two Sets when they have the same values; a Map of values that aren’t Equatable compares as the same object with==, but isn’t Equatable or Hashable, so it isn’t a key or found by value. A Map whose values areHashableis too, and so is a Set, with hashes that their order doesn’t change, so either can be a key. {key: value, ...}is a newMap<K, V>. K and V are the expected Map type’s, or else the keys’ and the values’ common types, as in an array literal.{}needs a type from where it’s used, likeLocal m: Map<String, Int> = {}. Each entry goes to the Map’sAdd, in order, so a repeated key keeps its last value.For key, value In maptakes each entry apart;For entry In mapgives each as a tuple.map[key]of a key that isn’t there panics where it’s read, unless the Map was made with a default, asMap<String, Int>(default: 0)is, which[]gives without adding the key, socounts[word] += 1counts. The default is one value, which every such read gives: one that’s an object or an array is shared, so to give each key its own, like an adjacency list’s array, useGetOrAdd.- Arrays, Stack, Queue, Deque, and PriorityQueue have
AddAll(values), adding each in turn, which for a PriorityQueue orders its heap once, and arraysInsertAll(index, values). These and the Set Methods that take values take anyIterable<T>, an array included, and read all of it first, so a collection can take its own; a Map’s AddAll takes a Map. - An array’s Methods that call a function, like
RemoveAll(test)orSort(before), go over what’s in the array as each step begins, as aFor ... Indoes: a function that adds or removes elements changes what’s left, and an element it takes away that the Method still needs panics where the Method was called. items.View(start, finish)is anArrayView<T>of the elements from start up to finish: it reads and sets them in the array, which it keeps alive, so it sees the array’s changes, and an element the array no longer has panics, naming the view’s index. ItsCountis the view’s own, which stays as the array changes. It hasview[i],View,ToArray(), and goes in aFor ... In. A slice is still a copy.AsReadOnly()gives an array, Map, or Set to read but not change, aReadOnlyArray<T>,ReadOnlyMap<K, V>, orReadOnlySet<T>, which sees the changes of what it shows and keeps it alive:Count,[],Contains,Get,Keys,Values,For ... In, and a copy throughToArray(),ToMap(), orToSet().- Arrays and std’s collection Classes are
Cloneable, withClone(). The Classes, Map, Set, Stack, Queue, Deque, PriorityQueue, and Pool, are alsoDefaultable, whoseDefaultValue()is a new empty one; a PriorityQueue is Defaultable when its values are Comparable. An array isn’t, since aT[]has no zero. - A
Pool<T>reuses removed values’ slots. Each slot counts how many times it has been reused, and aHandle<T>has that count, so an old Handle finds nothing in a reused slot. Going over a Pool gives the values in the order of their slots, and removing one while going over them is fine. A Handle’s Index is ReadOnly, and only a Pool makes one, so a Handle is always one the Pool gave; Handles areHashable, equal when they’re the same value’s. A Pool’s Clone has the same Handles, and a Pool of aT?keeps a Null as a value. - A Map spreads its keys over its buckets by mixing their Hash, so one that differs only in its
high bits, like
x << 32 | y, does as well as any.
More collections
| Type | What it’s for, and its members |
|---|---|
SortedMap<K, V> | keys in order, K: Comparable, in a B-tree: Add(key, value), which adds or replaces, map[key] and map[key] = value, Get(key) and Get(key, fallback), Contains, Remove(key), Clear, Keys() and Values() in order, First(), Last(), Floor(key), the largest key not above it, and Ceiling(key), the smallest not below it, which give a (K, V)?, and Range(low, high), from low up to high, without high; == when its values are Equatable |
SortedSet<T> | values in order, T: Comparable: Add(value) gives whether it’s new, Remove, Contains, Clear, First(), Last(), Floor, Ceiling, which give a T?, Range(low, high), ToArray() in order, and == |
RingBuffer<T> | the last Capacity values added, oldest first, for a history or a window: RingBuffer<T>(capacity), Add drops the oldest when it’s full (IsFull), AddAll, RemoveFirst, RemoveLast, First, Last, and their Try twins, ring[i] and ring[i] = value from the oldest, Contains, Clear, ToArray() |
Grid<T>, Grid3<T> | a fixed number of cells, Grid<T>(width, height, fill) and Grid3<T>(width, height, depth, fill): grid[x, y] and grid[x, y, z], read and set, Width, Height, Depth, InBounds, Neighbors4 and Neighbors8, or Neighbors6 and Neighbors26, the positions around one that are in the grid, in the order their comments give, Fill(value), ToArray() row by row and layer by layer, and ==; a position outside panics |
BitSet | a Set of Ints from 0 up, a bit each: Add(value), which gives whether it’s new and panics at a negative value, AddAll, Remove, Contains, Clear, First() and Last(), which give an Int?, Union, Intersect, Difference, and SymmetricDifference with another BitSet, AddAll, RemoveAll, and KeepOnly with one in place, IsSubsetOf, IsSupersetOf, Overlaps, ToArray() smallest first, ==, and Hash |
LruCache<K, V> | up to Capacity entries, LruCache<K, V>(capacity): Get(key) gives a V? and uses the key, Peek(key) doesn’t, Add(key, value) uses it and gives the least recently used entry when it had to go, Contains, Remove, Clear, Resize(capacity), Keys() and Values() from the least recently used |
Counter<T> | how many times each value was added: Add(value) and Add(value, times), AddAll, Remove(value) and Remove(value, times), counter[value], which is 0 for one that isn’t there, and counter[value] = count, Contains, Clear, Total, an Int64, MostCommon(count), Values(), ==, and Hash; a count that reaches 0 takes its value out, so one that comes back goes last, and a count past what an Int holds panics where it’s added |
MultiMap<K, V> | keys with one value or more: Add(key, value), AddAll(key, values), multi[key] and Get(key), which give a copy of its values, empty for none, Contains(key) and Contains(key, value), Remove(key, value), RemoveKey(key), Clear, KeyCount, Keys(), and ==; Count counts values, and it goes over each key with each value |
Trie<V> | String keys by their bytes: Add(key, value), which adds or replaces, trie[key] and trie[key] = value, Get(key) and Get(key, fallback), Contains, Remove, Clear, Keys() and Values() in order, HasPrefix(prefix), KeysWithPrefix(prefix), LongestPrefixOf(text), and == |
LinkedList<T> | values in nodes linked both ways: AddFirst, AddLast, Add, AddBefore(node, value), and AddAfter(node, value), which give the value’s LinkedListNode<T>, AddAll, RemoveNode(node), Remove(value), RemoveFirst, RemoveLast, First, Last, and their Try twins, Find(test) and FindLast(test), NodeOf(value) and LastNodeOf(value), the FirstNode and LastNode Properties, Contains, Clear, ToArray(), and ==; a node has Value, which can be set, Next, Previous, and List |
IntervalTree<K, V> | values over intervals from a low to a high, both in them, K: Comparable: Add(low, high, value), Remove(low, high, value), Contains(low, high, value), Clear, Overlapping(low, high) and Containing(key), which give (K, K, V)s by low and then by high, ToArray(), and == |
SpatialHash<T>, QuadTree<T>, Octree<T> | values at points of Floats: SpatialHash<T>(size), in square cells that size across, QuadTree<T>(minX, minY, maxX, maxY), and Octree<T> with Zs too: Add(value, x, y), Remove(value, x, y), Contains(value, x, y), Move(value, x, y, toX, toY), Clear, Query(minX, minY, maxX, maxY), the values in a box, its edges included, Near(x, y, radius), no more than radius away, and ToArray() |
ImmutableArray<T>, ImmutableMap<K, V>, ImmutableSet<T> | collections that never change, so they’re Sendable and go to any thread: Structs whose Add, AddAll, Set, Insert, Remove, and RemoveAt give a new one, sharing all but about log(Count) nodes with the old; From(values), array[i] and map[key], Contains, Get, Keys(), Values(), ToArray(), ToMap(), ==, and Hash where their values are Hashable |
- Each of these goes in a
For ... In, and the Classes areCloneable, withClone(); the immutable ones need neither Clone nor Clear, since a copy of one is the same collection and a new one starts empty. Those that start empty areDefaultabletoo: SortedMap, SortedSet, BitSet, Counter, MultiMap, Trie, LinkedList, IntervalTree, and the immutable ones. - A SortedMap holds 15 to 31 keys in a node but the root, so finding, adding, and removing a key
looks at about log₁₆(Count) nodes, each a binary search, and giving a key a new value or
removing one that isn’t there leaves the tree as it is. Its order is the keys’
<, which has no place for a key that isn’t equal to itself, like NaN: one panics where it’s added to a SortedMap or SortedSet, and finds nothing where it’s looked up. - A RingBuffer, a Grid, and a Grid3 have a fixed size, set when they’re made, and a size that isn’t 1 or more for a RingBuffer, or is negative or has more cells than an Int counts for a Grid, panics there; a Grid3 without cells along an axis has none. Giving a value a new value while going over them is fine.
- An LruCache keeps its entries in a Map in the order they were last used, so a use moves an entry to the end: going over it while using one panics, as adding one does.
- A Trie’s node per byte of its keys makes
HasPrefixandLongestPrefixOfgo down the prefix alone.HasPrefix("")is whether the Trie has a key. - A LinkedList’s node is the same object while it’s in the list, holds the next node, and holds
the one before weakly, so the list makes no cycles. A node removed, or of another list, panics
where it’s given to the list, and a removed node, or one of a list that’s cleared or gone, has
no
List,Next, orPrevious. ItsFirst,Last,Find, andRemovegive or take values, not nodes, and what gives or takes a node says so in its name. Unlike an array’s,FirstandLastpanic when the list is empty, as a Deque’s do, and their Try twins give aT?. - An IntervalTree is a balanced tree of the intervals, each node with the highest high under
it, so
Overlappingskips what ends too soon. A high below low, or an end that isn’t equal to itself, like NaN, panics where it’s added, and finds nothing where it’s searched for. - A spatial structure’s coordinates are Floats, and its distances and boxes are worked out in
Doubles, so none overflows; a NaN coordinate panics where it’s given, a negative or NaN radius
finds nothing, and an infinite one finds every point. A SpatialHash covers any space, a point
far out going in the last cell, while a QuadTree and an Octree hold only the points in their
box, which has to be finite, a point outside panicking where it’s added or moved to, and split
a node in four or eight past 8 points, up to 16 times. What a query finds, and the order a
For ... Ingoes in, is no order to count on. - The immutable collections are Structs over balanced trees of Shared Class nodes, so their
values, and an ImmutableMap’s keys, are
Sendable, and a key isHashabletoo. An ImmutableMap and an ImmutableSet go by their keys’ hashes, and keys of one hash in the order they were added.
Value
std.core’s Value is data of any shape, as the data formats have it: Null, a Bool, an integer,
a Double, a String, bytes, an Array of Values, or an Object of Values by key.
Local config = Value.Object()
config["name"] = Value("game")
config["ports"] = Value([Value(80), Value(443)])
Print(config, config.Path("ports[1]")?.AsInt() ?? 0) // {"name": "game", "ports": [80, 443]} 443- An integer is an Int64, or a UInt64 past an Int64’s largest, so none loses a digit. A New takes
each number type, so
Value(5)andValue(0.1)read as an Int and a Double, as written. - What it holds is its
Kind, and the getters give it as a type,AsInt()orAsString(), or Null for another kind.v["key"],v[0], andv.Path("a.b[0]")give aValue?, Null where there’s nothing, and an Object’sKeys()andContains(key)say which keys it has. - A Value is an object: a change to an Array or Object shows through every reference to it, and
Clone()copies one whole. None holds itself, inside it however deep, which a change that would make it panics at, but one can sit in several places of another, where a change to it shows in each, and which Clone copies into each. - Two Values are
==when they’re the same kind holding the same, an Object’s keys in any order; an integer isn’t a Double, soValue(1) != Value(1.0). Its text is JSON’s on one line, with bytes, which JSON doesn’t have, in hex between angle brackets, as<0aff>.
Report an issue with this page or with FutureBlitz on GitHub.