std/sequtils

    Dark Mode
Search:
  Source   Edit

Although this module has seq in its name, it implements operations not only for the seq type, but for three built-in container types under the openArray umbrella:

  • sequences
  • strings
  • array

The system module defines several common functions, such as:

  • newSeq[T] for creating new sequences of type T
  • @ for converting arrays and strings to sequences
  • add for adding new elements to strings and sequences
  • & for string and seq concatenation
  • in (alias for contains) and notin for checking if an item is in a container

This module builds upon that, providing additional functionality in form of procs, iterators and templates inspired by functional programming languages.

For functional style programming you have different options at your disposal:

Chaining of functions is possible thanks to the method call syntax.

Example:

import std/sequtils
import std/sugar

# Creating a sequence from 1 to 10, multiplying each member by 2,
# keeping only the members which are not divisible by 6.
let
  foo = toSeq(1..10).map(x => x * 2).filter(x => x mod 6 != 0)
  bar = toSeq(1..10).mapIt(it * 2).filterIt(it mod 6 != 0)
  baz = collect:
    for i in 1..10:
      let j = 2 * i
      if j mod 6 != 0:
        j

doAssert foo == bar
doAssert foo == baz
doAssert foo == @[2, 4, 8, 10, 14, 16, 20]

doAssert foo.any(x => x > 17)
doAssert not bar.allIt(it < 20)
doAssert foo.foldl(a + b) == 74 # sum of all members

Example:

import std/sequtils
from std/strutils import join

let
  vowels = @"aeiou"
  foo = "sequtils is an awesome module"

doAssert (vowels is seq[char]) and (vowels == @['a', 'e', 'i', 'o', 'u'])
doAssert foo.filterIt(it notin vowels).join == "sqtls s n wsm mdl"

See also

Procs

proc all[T](s: openArray[T]; pred: proc (x: T): bool {.closure.}): bool {.
    effectsOf: pred.}

Iterates through a container and checks if every item fulfills the predicate.

See also:

Example:

let numbers = @[1, 4, 5, 8, 9, 7, 4]
assert all(numbers, proc (x: int): bool = x < 10) == true
assert all(numbers, proc (x: int): bool = x < 9) == false
  Source   Edit
proc any[T](s: openArray[T]; pred: proc (x: T): bool {.closure.}): bool {.
    effectsOf: pred.}

Iterates through a container and checks if at least one item fulfills the predicate.

See also:

Example:

let numbers = @[1, 4, 5, 8, 9, 7, 4]
assert any(numbers, proc (x: int): bool = x > 8) == true
assert any(numbers, proc (x: int): bool = x > 9) == false
  Source   Edit
proc apply[T](s: openArray[T]; op: proc (x: T) {.closure.}) {.inline,
    effectsOf: op.}
Same as apply but for a proc that does not return anything and does not mutate s directly.

Example:

var message: string
apply([0, 1, 2, 3, 4], proc(item: int) = message.addInt item)
assert message == "01234"
  Source   Edit
proc apply[T](s: var openArray[T]; op: proc (x: T): T {.closure.}) {.inline,
    effectsOf: op.}

Applies op to every item in s modifying it directly.

Note that the container s must be declared as a var and it is required for your input and output types to be the same, since s is modified in-place. The parameter function takes and returns a T type variable.

See also:

Example:

var a = @["1", "2", "3", "4"]
apply(a, proc(x: string): string = x & "42")
assert a == @["142", "242", "342", "442"]
  Source   Edit
proc apply[T](s: var openArray[T]; op: proc (x: var T) {.closure.}) {.inline,
    effectsOf: op.}

Applies op to every item in s, modifying it directly.

Note that the container s must be declared as a var, since s is modified in-place. The parameter function takes a var T type parameter.

See also:

Example:

var a = @["1", "2", "3", "4"]
apply(a, proc(x: var string) = x &= "42")
assert a == @["142", "242", "342", "442"]
  Source   Edit
func concat[T](seqs: varargs[seq[T]]): seq[T]

Takes several sequences' items and returns them inside a new sequence. All sequences must be of the same type.

See also:

Example:

let
  s1 = @[1, 2, 3]
  s2 = @[4, 5]
  s3 = @[6, 7]
  total = concat(s1, s2, s3)
assert total == @[1, 2, 3, 4, 5, 6, 7]
  Source   Edit
func count[T](s: openArray[T]; x: T): int
Returns the number of occurrences of the item x in the container s.

Example:

let
  a = @[1, 2, 2, 3, 2, 4, 2]
  b = "abracadabra"
assert count(a, 2) == 4
assert count(a, 99) == 0
assert count(b, 'r') == 2
  Source   Edit
func cycle[T](s: openArray[T]; n: Natural): seq[T]
Returns a new sequence with the items of the container s repeated n times. n must be a non-negative number (zero or more).

Example:

let
  s = @[1, 2, 3]
  total = s.cycle(3)
assert total == @[1, 2, 3, 1, 2, 3, 1, 2, 3]
  Source   Edit
func deduplicate[T](s: openArray[T]; isSorted: bool = false): seq[T]

Returns a new sequence without duplicates.

Setting the optional argument isSorted to true (default: false) uses a faster algorithm for deduplication.

Example:

let
  dup1 = @[1, 1, 3, 4, 2, 2, 8, 1, 4]
  dup2 = @["a", "a", "c", "d", "d"]
  unique1 = deduplicate(dup1)
  unique2 = deduplicate(dup2, isSorted = true)
assert unique1 == @[1, 3, 4, 2, 8]
assert unique2 == @["a", "c", "d"]
  Source   Edit
func delete[T](s: var seq[T]; first, last: Natural) {.
    ...deprecated: "use `delete(s, first..last)`".}
Deprecated: use `delete(s, first..last)`
Deletes the items of a sequence s at positions first..last (including both ends of the range). This modifies s itself, it does not return a copy.

Example: cmd: --warning:deprecated:off

let outcome = @[1, 1, 1, 1, 1, 1, 1, 1]
var dest = @[1, 1, 1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1]
dest.delete(3, 8)
assert outcome == dest
  Source   Edit
func delete[T](s: var seq[T]; slice: Slice[int])

Deletes the items s[slice], raising IndexDefect if the slice contains elements out of range.

This operation moves all elements after s[slice] in linear time.

Example:

var a = @[10, 11, 12, 13, 14]
doAssertRaises(IndexDefect): a.delete(4..5)
assert a == @[10, 11, 12, 13, 14]
a.delete(4..4)
assert a == @[10, 11, 12, 13]
a.delete(1..2)
assert a == @[10, 13]
a.delete(1..<1) # empty slice
assert a == @[10, 13]
  Source   Edit
func distribute[T](s: seq[T]; num: Positive; spread = true): seq[seq[T]]

Splits and distributes a sequence s into num sub-sequences.

Returns a sequence of num sequences. For some input values this is the inverse of the concat func. The input sequence s can be empty, which will produce num empty sequences.

If spread is false and the length of s is not a multiple of num, the func will max out the first sub-sequence with 1 + len(s) div num entries, leaving the remainder of elements to the last sequence.

On the other hand, if spread is true, the func will distribute evenly the remainder of the division across all sequences, which makes the result more suited to multithreading where you are passing equal sized work units to a thread pool and want to maximize core usage.

Example:

let numbers = @[1, 2, 3, 4, 5, 6, 7]
assert numbers.distribute(3) == @[@[1, 2, 3], @[4, 5], @[6, 7]]
assert numbers.distribute(3, false) == @[@[1, 2, 3], @[4, 5, 6], @[7]]
assert numbers.distribute(6)[0] == @[1, 2]
assert numbers.distribute(6)[1] == @[3]
  Source   Edit
proc filter[T](s: openArray[T]; pred: proc (x: T): bool {.closure.}): seq[T] {.
    inline, effectsOf: pred.}

Returns a new sequence with all the items of s that fulfill the predicate pred (a function that returns a bool).

Instead of using map and filter, consider using the collect macro from the sugar module.

See also:

Example:

let
  colors = @["red", "yellow", "black"]
  f1 = filter(colors, proc(x: string): bool = x.len < 6)
  f2 = filter(colors, proc(x: string): bool = x.contains('y'))
assert f1 == @["red", "black"]
assert f2 == @["yellow"]
  Source   Edit
func insert[T](dest: var seq[T]; src: openArray[T]; pos = 0)

Inserts items from src into dest at position pos. This modifies dest itself, it does not return a copy.

Note that the elements of src and dest must be of the same type.

Example:

var dest = @[1, 1, 1, 1, 1, 1, 1, 1]
let
  src = @[2, 2, 2, 2, 2, 2]
  outcome = @[1, 1, 1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1]
dest.insert(src, 3)
assert dest == outcome
  Source   Edit
proc keepIf[T](s: var seq[T]; pred: proc (x: T): bool {.closure.}) {.inline,
    effectsOf: pred.}

Keeps the items in the passed sequence s if they fulfill the predicate pred (a function that returns a bool).

Note that s must be declared as a var.

Similar to the filter proc, but modifies the sequence directly.

See also:

Example:

var floats = @[13.0, 12.5, 5.8, 2.0, 6.1, 9.9, 10.1]
keepIf(floats, proc(x: float): bool = x > 10)
assert floats == @[13.0, 12.5, 10.1]
  Source   Edit
proc map[T, S](s: openArray[T]; op: proc (x: T): S {.closure.}): seq[S] {.
    inline, effectsOf: op.}

Returns a new sequence with the results of the op proc applied to every item in the container s.

Since the input is not modified, you can use it to transform the type of the elements in the input container.

Instead of using map and filter, consider using the collect macro from the sugar module.

See also:

Example:

let
  a = @[1, 2, 3, 4]
  b = map(a, proc(x: int): string = $x)
assert b == @["1", "2", "3", "4"]
  Source   Edit
func maxIndex[T](s: openArray[T]): int
Returns the index of the maximum value of s. T needs to have a < operator.

Example:

let
  a = @[1, 2, 3, 4]
  b = @[6, 5, 4, 3]
  c = [2, -7, 8, -5]
  d = "ziggy"
assert maxIndex(a) == 3
assert maxIndex(b) == 0
assert maxIndex(c) == 2
assert maxIndex(d) == 0
  Source   Edit
func minIndex[T](s: openArray[T]): int
Returns the index of the minimum value of s. T needs to have a < operator.

Example:

let
  a = @[1, 2, 3, 4]
  b = @[6, 5, 4, 3]
  c = [2, -7, 8, -5]
  d = "ziggy"
assert minIndex(a) == 0
assert minIndex(b) == 3
assert minIndex(c) == 1
assert minIndex(d) == 2
  Source   Edit
func repeat[T](x: T; n: Natural): seq[T]
Returns a new sequence with the item x repeated n times. n must be a non-negative number (zero or more).

Example:

let
  total = repeat(5, 3)
assert total == @[5, 5, 5]
  Source   Edit
func unzip[S, T](s: openArray[(S, T)]): (seq[S], seq[T])
Returns a tuple of two sequences split out from a sequence of 2-field tuples.

Example:

let
  zipped = @[(1, 'a'), (2, 'b'), (3, 'c')]
  unzipped1 = @[1, 2, 3]
  unzipped2 = @['a', 'b', 'c']
assert zipped.unzip() == (unzipped1, unzipped2)
assert zip(unzipped1, unzipped2).unzip() == (unzipped1, unzipped2)
  Source   Edit
func zip[S, T](s1: openArray[S]; s2: openArray[T]): seq[(S, T)]

Returns a new sequence with a combination of the two input containers.

The input containers can be of different types. If one container is shorter, the remaining items in the longer container are discarded.

Note: For Nim 1.0.x and older version, zip returned a seq of named tuples with fields a and b. For Nim versions 1.1.x and newer, zip returns a seq of unnamed tuples.

Example:

let
  short = @[1, 2, 3]
  long = @[6, 5, 4, 3, 2, 1]
  words = @["one", "two", "three"]
  letters = "abcd"
  zip1 = zip(short, long)
  zip2 = zip(short, words)
assert zip1 == @[(1, 6), (2, 5), (3, 4)]
assert zip2 == @[(1, "one"), (2, "two"), (3, "three")]
assert zip1[2][0] == 3
assert zip2[1][1] == "two"
when (NimMajor, NimMinor) <= (1, 0):
  let
    zip3 = zip(long, letters)
  assert zip3 == @[(a: 6, b: 'a'), (5, 'b'), (4, 'c'), (3, 'd')]
  assert zip3[0].b == 'a'
else:
  let
    zip3: seq[tuple[num: int, letter: char]] = zip(long, letters)
  assert zip3 == @[(6, 'a'), (5, 'b'), (4, 'c'), (3, 'd')]
  assert zip3[0].letter == 'a'
  Source   Edit

Iterators

iterator filter[T](s: openArray[T]; pred: proc (x: T): bool {.closure.}): T {.
    effectsOf: pred.}

Iterates through a container s and yields every item that fulfills the predicate pred (a function that returns a bool).

Instead of using map and filter, consider using the collect macro from the sugar module.

See also:

Example:

let numbers = @[1, 4, 5, 8, 9, 7, 4]
var evens = newSeq[int]()
for n in filter(numbers, proc (x: int): bool = x mod 2 == 0):
  evens.add(n)
assert evens == @[4, 8, 4]
  Source   Edit
iterator items[T](xs: iterator (): T): T
Iterates over each element yielded by a closure iterator. This may not seem particularly useful on its own, but this allows closure iterators to be used by the mapIt, filterIt, allIt, anyIt, etc. templates.   Source   Edit

Macros

macro mapLiterals(constructor, op: untyped; nested = true): untyped
Applies op to each of the atomic literals like 3 or "abc" in the specified constructor AST. This can be used to map every array element to some target type:

Example:

let x = mapLiterals([0.1, 1.2, 2.3, 3.4], int)
doAssert x is array[4, int]
doAssert x == [int(0.1), int(1.2), int(2.3), int(3.4)]
If nested is true (which is the default), the literals are replaced everywhere in the constructor AST, otherwise only the first level is considered:

Example:

let a = mapLiterals((1.2, (2.3, 3.4), 4.8), int)
let b = mapLiterals((1.2, (2.3, 3.4), 4.8), int, nested=false)
assert a == (1, (2, 3), 4)
assert b == (1, (2.3, 3.4), 4)

let c = mapLiterals((1, (2, 3), 4, (5, 6)), `$`)
let d = mapLiterals((1, (2, 3), 4, (5, 6)), `$`, nested=false)
assert c == ("1", ("2", "3"), "4", ("5", "6"))
assert d == ("1", (2, 3), "4", (5, 6))
There are no constraints for the constructor AST, it works for nested tuples of arrays of sets etc.   Source   Edit

Templates

template allIt(s, pred: untyped): bool

Iterates through a container and checks if every item fulfills the predicate.

Unlike the all proc, the predicate needs to be an expression using the it variable for testing, like: allIt("abba", it == 'a').

See also:

Example:

let numbers = @[1, 4, 5, 8, 9, 7, 4]
assert numbers.allIt(it < 10) == true
assert numbers.allIt(it < 9) == false
  Source   Edit
template anyIt(s, pred: untyped): bool

Iterates through a container and checks if at least one item fulfills the predicate.

Unlike the any proc, the predicate needs to be an expression using the it variable for testing, like: anyIt("abba", it == 'a').

See also:

Example:

let numbers = @[1, 4, 5, 8, 9, 7, 4]
assert numbers.anyIt(it > 8) == true
assert numbers.anyIt(it > 9) == false
  Source   Edit
template applyIt(varSeq, op: untyped)

Convenience template around the mutable apply proc to reduce typing.

The template injects the it variable which you can use directly in an expression. The expression has to return the same type as the elements of the sequence you are mutating.

See also:

Example:

var nums = @[1, 2, 3, 4]
nums.applyIt(it * 3)
assert nums[0] + nums[3] == 15
  Source   Edit
template countIt(s, pred: untyped): int

Returns a count of all the items that fulfill the predicate.

The predicate needs to be an expression using the it variable for testing, like: countIt(@[1, 2, 3], it > 2).

Example:

let numbers = @[-3, -2, -1, 0, 1, 2, 3, 4, 5, 6]
iterator iota(n: int): int =
  for i in 0..<n: yield i
assert numbers.countIt(it < 0) == 3
assert countIt(iota(10), it < 2) == 2
  Source   Edit
template filterIt(s, pred: untyped): untyped

Returns a new sequence with all the items of s that fulfill the predicate pred.

Unlike the filter proc and filter iterator, the predicate needs to be an expression using the it variable for testing, like: filterIt("abcxyz", it == 'x').

Instead of using mapIt and filterIt, consider using the collect macro from the sugar module.

See also:

Example:

let
  temperatures = @[-272.15, -2.0, 24.5, 44.31, 99.9, -113.44]
  acceptable = temperatures.filterIt(it < 50 and it > -10)
  notAcceptable = temperatures.filterIt(it > 50 or it < -10)
assert acceptable == @[-2.0, 24.5, 44.31]
assert notAcceptable == @[-272.15, 99.9, -113.44]
  Source   Edit
template foldl(sequence, operation, first): untyped

Template to fold a sequence from left to right, returning the accumulation.

This version of foldl gets a starting parameter. This makes it possible to accumulate the sequence into a different type than the sequence elements.

The operation parameter should be an expression which uses the variables a and b for each step of the fold. The first parameter is the start value (the first a) and therefor defines the type of the result.

See also:

Example:

let
  numbers = @[0, 8, 1, 5]
  digits = foldl(numbers, a & (chr(b + ord('0'))), "")
assert digits == "0815"
  Source   Edit
template foldl(sequence, operation: untyped): untyped

Template to fold a sequence from left to right, returning the accumulation.

The sequence is required to have at least a single element. Debug versions of your program will assert in this situation but release versions will happily go ahead. If the sequence has a single element it will be returned without applying operation.

The operation parameter should be an expression which uses the variables a and b for each step of the fold. Since this is a left fold, for non associative binary operations like subtraction think that the sequence of numbers 1, 2 and 3 will be parenthesized as (((1) - 2) - 3).

See also:

Example:

let
  numbers = @[5, 9, 11]
  addition = foldl(numbers, a + b)
  subtraction = foldl(numbers, a - b)
  multiplication = foldl(numbers, a * b)
  words = @["nim", "is", "cool"]
  concatenation = foldl(words, a & b)
  procs = @["proc", "Is", "Also", "Fine"]


func foo(acc, cur: string): string =
  result = acc & cur

assert addition == 25, "Addition is (((5)+9)+11)"
assert subtraction == -15, "Subtraction is (((5)-9)-11)"
assert multiplication == 495, "Multiplication is (((5)*9)*11)"
assert concatenation == "nimiscool"
assert foldl(procs, foo(a, b)) == "procIsAlsoFine"
  Source   Edit
template foldr(sequence, operation: untyped): untyped

Template to fold a sequence from right to left, returning the accumulation.

The sequence is required to have at least a single element. Debug versions of your program will assert in this situation but release versions will happily go ahead. If the sequence has a single element it will be returned without applying operation.

The operation parameter should be an expression which uses the variables a and b for each step of the fold. Since this is a right fold, for non associative binary operations like subtraction think that the sequence of numbers 1, 2 and 3 will be parenthesized as (1 - (2 - (3))).

See also:

Example:

let
  numbers = @[5, 9, 11]
  addition = foldr(numbers, a + b)
  subtraction = foldr(numbers, a - b)
  multiplication = foldr(numbers, a * b)
  words = @["nim", "is", "cool"]
  concatenation = foldr(words, a & b)
assert addition == 25, "Addition is (5+(9+(11)))"
assert subtraction == 7, "Subtraction is (5-(9-(11)))"
assert multiplication == 495, "Multiplication is (5*(9*(11)))"
assert concatenation == "nimiscool"
  Source   Edit
template keepItIf(varSeq: seq; pred: untyped)

Keeps the items in the passed sequence (must be declared as a var) if they fulfill the predicate.

Unlike the keepIf proc, the predicate needs to be an expression using the it variable for testing, like: keepItIf("abcxyz", it == 'x').

See also:

Example:

var candidates = @["foo", "bar", "baz", "foobar"]
candidates.keepItIf(it.len == 3 and it[0] == 'b')
assert candidates == @["bar", "baz"]
  Source   Edit
template mapIt(s: typed; op: untyped): untyped

Returns a new sequence with the results of the op proc applied to every item in the container s.

Since the input is not modified you can use it to transform the type of the elements in the input container.

The template injects the it variable which you can use directly in an expression.

Instead of using mapIt and filterIt, consider using the collect macro from the sugar module.

See also:

Example:

let
  nums = @[1, 2, 3, 4]
  strings = nums.mapIt($(4 * it))
assert strings == @["4", "8", "12", "16"]
  Source   Edit
template newSeqWith(len: int; init: untyped): untyped

Creates a new seq of length len, calling init to initialize each value of the seq.

Useful for creating "2D" seqs - seqs containing other seqs or to populate fields of the created seq.

Example:

## Creates a seq containing 5 bool seqs, each of length of 3.
var seq2D = newSeqWith(5, newSeq[bool](3))
assert seq2D.len == 5
assert seq2D[0].len == 3
assert seq2D[4][2] == false

## Creates a seq with random numbers
import std/random
var seqRand = newSeqWith(20, rand(1.0))
assert seqRand[0] != seqRand[1]
  Source   Edit
template toSeq(iter: untyped): untyped
Transforms any iterable (anything that can be iterated over, e.g. with a for-loop) into a sequence.

Example:

let
  myRange = 1..5
  mySet: set[int8] = {5'i8, 3, 1}
assert typeof(myRange) is HSlice[system.int, system.int]
assert typeof(mySet) is set[int8]

let
  mySeq1 = toSeq(myRange)
  mySeq2 = toSeq(mySet)
assert mySeq1 == @[1, 2, 3, 4, 5]
assert mySeq2 == @[1'i8, 3, 5]
  Source   Edit