Collections

Nim's standard library offers a small, well-chosen set of containers: fixed arrays, dynamic sequences, sets of ordinals, hash sets and hash tables. Choosing the right one is mostly a question of whether the size is known at compile time and whether you need lookup by key.

Arrays and Sequences

An array has a size fixed at compile time and no heap allocation. A seq grows at runtime and is the default choice for lists of things.

Fixed-Size Arrays

import std/strutils

var week: array[7, string] = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
echo week.len, " days"        # array length is part of the type, known at compile time
echo week[0], " -> ", week[^1]  # '^1' means the last element, counting from the end

# Iterating with an index is unnecessary when you only need the values.
for day in week:
  if day.len == 6 and day.endsWith("day"):
    echo "practice day: ", day

# Arrays can be nested: a grid stored inline, with no dynamic allocation.
var grid: array[3, array[3, int]]
grid[1][2] = 7               # rows first, columns second
echo grid[1][2]

Dynamic Sequences

var nums = @[3, 1, 2]        # '@[...]' builds a seq; the type is seq[int]
nums.add(4)                  # append to the end — amortized constant time
nums.insert(0)               # insert at index 0 (the default): shifts the rest
echo nums.len, " ", nums     # 5 @[0, 3, 1, 2, 4]

nums.delete(0)               # remove by index; order of the rest is preserved
echo nums                    # @[3, 1, 2, 4]

# Slicing copies a sub-range; '^1' addresses the final element.
let tail = nums[1 .. ^1]
echo tail                    # @[1, 2, 4]

# newSeq allocates a known size up front, avoiding repeated reallocation.
var zeros = newSeq[int](4)
zeros[2] = 9
echo zeros                   # @[0, 0, 9, 0]

# add can move a whole seq at once, which is how small collections compose.
zeros.add(nums[0 .. 1])
echo zeros                   # @[0, 0, 9, 0, 3, 1]

Sets, Hash Sets and Tables

A set is a bitset over a small ordinal type — compact, fast and allocation-free. When the domain is not a small ordinal, use HashSet; when values must be looked up by key, use Table.

Sets of Ordinals

type Flag = enum flRead, flWrite, flExec

var permissions: set[Flag] = {flRead}    # a set literal uses braces
permissions.incl(flWrite)                 # add a member
permissions.excl(flRead)                  # remove a member
echo permissions.contains(flWrite)        # true
echo flExec in permissions                # false — 'in' is the membership test

# Set algebra works the way mathematics does.
let cpu = {flRead, flExec}
let both = permissions + cpu              # union
let shared = permissions * cpu            # intersection
echo both.len, " ", shared.len            # 2 1

# char sets are the classic use: validating input without a chain of comparisons.
const vowels = {'a', 'e', 'i', 'o', 'u'}
echo 'i' in vowels, " ", 'z' in vowels    # true false

Hash Tables and Hash Sets

import std/tables

var stock = initTable[string, int]()      # key: string, value: int
stock["apples"] = 3                       # '[]=' inserts or overwrites
stock["pears"] = 5
echo stock.len, " kinds"                  # 2
echo stock.getOrDefault("plums", 0)       # 0 — a missing key returns the default

if stock.hasKey("apples"):                # test before reading when absence matters
  stock["apples"] = stock["apples"] - 1   # no KeyError: the key is known to exist

for name, count in stock.pairs:           # pairs walks keys and values together
  echo name, " = ", count

# Table keys use '==' and 'hash'; define hash() in std/hashes for custom keys.
stock.clear()
echo stock.len                            # 0

Iteration Helpers and Algorithms

Two modules remove most manual loops: std/algorithm for ordering and searching, and std/sequtils for transforming sequences into new sequences.

Sorting and Searching

import std/algorithm

var scores = @[42, 7, 19, 42, 3]
scores.sort()                             # ascending, in place
echo scores                               # @[3, 7, 19, 42, 42]

scores.reverse()                          # also in place
echo scores                               # @[42, 42, 19, 7, 3]

echo scores.binarySearch(19)              # index of 19 in the sorted sequence

# A custom comparator sorts by any rule you can express as a bool.
var names = @["Grace", "Ada", "Linus"]
names.sort(proc (a, b: string): int = cmp(a.len, b.len))  # cmp: -1, 0, 1
echo names                                # @["Ada", "Linus", "Grace"]

Transforming with sequtils

import std/sequtils

let values = @[1, 2, 3, 4, 5]
echo values.mapIt(it * it)                # @[1, 4, 9, 16, 25]
echo values.filterIt(it mod 2 == 1)       # @[1, 3, 5] — keep odd numbers
echo values.foldl(a + b)                  # 15 — reduce with '+'

# Mutating in place: 'mitems' yields mutable references into the sequence.
var buffer = @[1, 2, 3]
for item in buffer.mitems:
  item *= 10                              # writes back into the sequence
echo buffer                               # @[10, 20, 30]

# zip pairs two sequences; walking both with one loop keeps indices in sync.
echo zip(@["a", "b"], @[1, 2])            # @[("a", 1), ("b", 2)]

Practice

Read a list of words into a seq[string], build a Table[string, int] that counts how often each appears, then print the five most frequent by sorting the pairs with a custom comparator. That single exercise touches sequences, tables, iteration and algorithms — continue with Strings & Text.