Collections

Refingere comes with the collections most programs need, all generic and all built in: lists, tuples, maps, sets, stacks and queues, plus tables and graphs. They share one set of methods through the Iterable interface, so what works on one mostly works on the others. The Types reference lists every method of every collection.

Arrays

Array is the ordered, growable list, written with square brackets. An empty literal has no elements to infer a type from, so it needs one written out. Array.filled and Array.generate build one of a given size. push and pop change an array in place, and set replaces one element.

Reading elements

Indexing never stops the program. xs[i] gives the element, or undefined when there isn't one, so its type is T? and the missing case has to be handled - most often with ??. Methods that can't give a result for every input work the same way: first, last, at and find return T?, and slice returns an error. A subscript, xs₀, is another way to write xs[0]. See Index Access.

Joining and spreading

++ joins two arrays into a new one - it also joins strings and tuples. ..xs inside a list literal spreads every element of xs in place, and set xs ++= ys appends in place.

Working with the elements

Most work on a collection is a chain of methods taking lambdas, rather than a loop: map changes each element, filter keeps some, sortedBy orders them, groupBy collects them into a map, and find, any and all ask questions. in checks whether a collection contains a value. Methods without a result for an empty collection, like max, return an EmptyError. sort and sortBy sort in place, and sorted and sortedBy return a sorted copy.

Tuples

A tuple groups a fixed number of values, each of its own type. It's the lightweight way to return several values from a function, or to key a map by two things at once. Tuples are usually taken apart with a pattern rather than read by position. When the parts deserve names, make a struct - see Types.

Maps

Map looks values up by key, and Map.of builds one from (key, value) tuples. Like an array index, map[key] gives undefined for a missing key. update changes a value based on the current one, or adds it when there's none - the usual way to count things. Keys have to be Hashable, which the builtin types are and your own types can derive. BiMap is a map that can be looked up in both directions.

Sets, stacks and queues

A Set holds each value once, and has the operations of sets in mathematics: intersection, difference, isSubsetOf, and so on.

Stack takes elements back in the opposite order they went in, Queue in the same order, and Deque from either end. None of them can be indexed - they say how they're meant to be used.

onAdd gives a Queue or Deque a handler: a function run with each item added from then on - by enqueue and enqueueAll, or pushFront and pushBack - right after it's added, before that call returns. The handler only looks: the item stays where it was put, for whoever takes it out. Several handlers run in the order they were given. A clone, or a queue made with ++, starts with none.

For a queue that only passes things on, setClearsAfterHandlers(true) empties it each time its handlers have run for an added item - so nothing piles up that nobody will take out. It's off at first, and does nothing while there's no handler: without one, what's added stays.

Iterators

Array methods like map and filter build a new array at each step. An Iterator has the same methods, but is lazy: nothing is computed until a value is asked for, and only as many as are needed. collect() gathers what's left into an array. Ranges, map entries and generators are all iterators, and iterator() makes one from any collection. Use one when the data is large, endless, or only partly needed. See Generators.

Priority queues, sorted maps, tries and caches

Four collections keep their elements in an order of their own. A PriorityQueue gives back the smallest element first - or whichever its withOrder comparison puts first - whatever order they went in: the queue of a scheduler or a search. A SortedMap keeps its keys sorted, so it can answer what a Map can't: the first and last keys, the nearest key at or below a value, every key in a range. A Trie holds String keys a codepoint at a time, for completion (withPrefix) and routing (longestPrefixOf). An LruCache holds at most so many entries and drops the least recently used to make room - get only looks, use and getOrInsert count as a use.

Statistics

Every collection of numbers has average, median, percentile, variance and standardDeviation (of the whole population) - each an EmptyError for no numbers. frequencies counts each element, and an iterator's chunkBy and distinctBy group and thin elements by a key.

Tables and networks

Two collections cover what programs usually reach for a database or a library for. Table holds rows of a struct, and queries them like SQL: where, orderBy, groupBy, join, and a key for fast lookups. See Library Catalogue. Network<Id, Data, Edge> is a graph of nodes and edges, with paths, reachability, cycles and topological sorting built in. Each node carries Data and each edge Edge - left out, it's Undefined, edges with nothing on them, as in Network<String, Undefined> here. See Metro Routes.

Beyond the inner join, leftJoin keeps every row - its match is undefined when nothing matches - and matching and notMatching keep only the rows that do or don't have one. summarize is a groupBy with what each group comes to, as a new table. union, intersection and difference combine two tables of the same rows, each row once - ++ keeps repeats - and distinctBy keeps the first row for each key. addIndex keeps the rows by one field as they change, so lookup on that field doesn't look at every row. format lays a table out as text, and Csv.encode writes it as CSV that Csv.decode reads back. Both take their columns from Row's fields, which are known when the program compiles - so a table with no rows still has its header.

When edges cost something - minutes, kilometres, money - cheapestPath finds the path with the least total cost (Dijkstra's algorithm, or A* when given an estimate of the cost left), distances the least cost to every node, and minimumSpanningTree the cheapest edges that keep every node joined. The costs come from a function of an edge's two ends - here the data the edge carries. stronglyConnected finds the groups of nodes that can all reach each other.