itertools

This package is a Nim rewrite of a very popular Python module of the same name.

It also includes some of the iterators from iterutils.

Installation

nimble install itertools

Required Nim version is at least 0.18.0.

Supported iterators

  • infinite iterators:
    • count
    • cycle
    • repeat
  • terminating iterators:
    • accumulate
    • chain
    • compress
    • dropWhile
    • filterFalse
    • groupBy
    • groupConsecutive
    • islice
    • takeWhile
  • combinatoric iterators:
    • product
    • distinctPermutations
    • permutations
    • combinations
  • iterutils iterators:
    • chunked
    • windowed
    • pairwise
    • unique

Iterators

iterator count[T: SomeNumber](start: T; step: SomeNumber = 1): T

Infinite iterator, counts from a start to infinity with a step step-size.

The default value of step is one.

Example:

var
  s1: seq[int] = @[]
  s2: seq[float] = @[]
  s3: seq[int] = @[]
  s4: seq[float] = @[]
  s5: seq[float] = @[]
for i in count(7): # int, default
  if i > 10: break
  s1.add(i)
for i in count(9.4): # float, default
  if i > 12: break
  s2.add(i)
for i in count(5, 2): # int, int
  if i > 12:
    break
  s3.add(i)
for i in count(9.6, 2.7): # float, float
  if i > 17:
    break
  s4.add(i)
for i in count(11.6, 3): # float, int
  if i > 20: break
  s5.add(i)
doAssert s1 == @[7, 8, 9, 10]
doAssert s2 == @[9.4, 10.4, 11.4]
doAssert s3 == @[5, 7, 9, 11]
doAssert s4 == @[9.6, 12.3, 15.0]
doAssert s5 == @[11.6, 14.6, 17.6]
iterator cycle[T](s: openArray[T]): T
Infinite iterator, cycles through the members of a sequence -- when it gets to the end of it, it starts again from the beginning.

Example:

let
  a = @[1, 3, 9, 5]
  b = @[2.0, 7.5, 11.3]
  c = "axm"
  d = @["me", "myself", "I"]
var
  s1: seq[int] = @[]
  s2: seq[float] = @[]
  s3: seq[char] = @[]
  s4: seq[string] = @[]
for i in a.cycle:
  if s1.len > 5: break
  s1.add(i)
for i in b.cycle:
  if s2.len > 4: break
  s2.add(i)
for i in c.cycle:
  if s3.len > 4: break
  s3.add(i)
for i in d.cycle:
  if s4.len > 5: break
  s4.add(i)
doAssert s1 == @[1, 3, 9, 5, 1, 3]
doAssert s2 == @[2.0, 7.5, 11.3, 2.0, 7.5]
doAssert s3 == @['a', 'x', 'm', 'a', 'x']
doAssert s4 == @["me", "myself", "I", "me", "myself", "I"]
iterator repeat[T](x: T; times = -1): T

Infinite iterator which yields an object x infinite number of times if times is not specified.

If times is specified, it runs that number of times.

Example:

let
  a = 3
  b = 2.7
  c = "Nim"
  d = @[1, 2]
var
  s1: seq[int] = @[]
  s2: seq[float] = @[]
  s3: seq[string] = @[]
  s4: seq[seq[int]] = @[]
for i in a.repeat:
  if s1.len > 5: break
  s1.add(i)
for i in b.repeat(5):
  s2.add(i)
for i in c.repeat:
  if s3.len > 4: break
  s3.add(i)
for i in d.repeat(3):
  s4.add(i)
doAssert s1 == @[3, 3, 3, 3, 3, 3]
doAssert s2 == @[2.7, 2.7, 2.7, 2.7, 2.7]
doAssert s3 == @["Nim", "Nim", "Nim", "Nim", "Nim"]
doAssert s4 == @[@[1, 2], @[1, 2], @[1, 2]]
iterator accumulate[T](s: openArray[T]; f: proc (a, b: T): T): T

Iterator which yields accumulated results of binary function f.

The result of f must be of the same type as members of s. The first yielded value is the first member of s.

Example:

let
  a = @[1, 3, 7, 5, 4]
  b = [1.0, 2.5, 6, 4, 5]
var
  s1: seq[int] = @[]
  s2: seq[float] = @[]
proc myadd(x, y: int): int = x + y
proc mymult(x, y: float): float = x * y
for x in accumulate(a, myadd):
  s1.add(x)
for x in accumulate(b, mymult):
  s2.add(x)
doAssert s1 == @[1, 4, 11, 16, 20]
doAssert s2 == @[1.0, 2.5, 15.0, 60.0, 300.0]
iterator chain[T](xs: varargs[seq[T]]): T
Iterator which yields elements of each passed sequence.

Example:

let
  a = @[1, 5, 4]
  b = @[9, 8, 7]
  c = @[22, 33, 44]
var
  s1: seq[int] = @[]
  s2: seq[int] = @[]
  s3: seq[int] = @[]
for x in chain(a):
  s1.add(x)
for x in chain(b, c):
  s2.add(x)
for x in chain(c, a, b):
  s3.add(x)
doAssert s1 == @[1, 5, 4]
doAssert s2 == @[9, 8, 7, 22, 33, 44]
doAssert s3 == @[22, 33, 44, 1, 5, 4, 9, 8, 7]
iterator chain(xs: varargs[string]): char {...}{.raises: [], tags: [].}
Iterator which yields characters of each passed string.

Example:

let
  a = "abc"
  b = "xyz"
var s1: string = ""
for x in chain(a, b):
  s1.add x
doAssert s1 == "abcxyz"
iterator compress[T](s: openArray[T]; b: openArray[bool]): T

Iterator which yields only those elements of a sequence s for which the element of a selector b is true.

Stops as soon as either s or b are exhausted.

Example:

let
  a = @[1, 2, 3, 4, 5, 6, 7, 8, 9]
  b = [9.5, 8.1, 7.3]
  c = @['a', 'b', 'c', 'd', 'e']
  d = [true, false, true, true, false, true]
var
  s1: seq[int] = @[]
  s2: seq[float] = @[]
  s3: seq[char] = @[]
for x in compress(a, d):
  s1.add(x)
for x in compress(b, d):
  s2.add(x)
for x in compress(c, d):
  s3.add(x)
doAssert s1 == @[1, 3, 4, 6]
doAssert s2 == @[9.5, 7.3]
doAssert s3 == @['a', 'c', 'd']
iterator dropWhile[T](s: openArray[T]; f: proc (a: T): bool): T
Iterator which drops the elements from a sequence s as long as the predicate is true. Afterwards, it returns every element.

Example:

let
  a = @[1, 3, 7, 2, 1, 4]
  b = ['a', 'd', 'h', 'd']
var
  s1: seq[int] = @[]
  s2: seq[int] = @[]
  s3: seq[char] = @[]
proc myodd(a: int): bool = a mod 2 == 1
proc mysmall(a: int): bool = a < 7
proc mysmall(a: char): bool = a < 'h'

for x in dropWhile(a, myodd):
  s1.add(x)
for x in dropWhile(a, mysmall):
  s2.add(x)
for x in dropWhile(b, mysmall):
  s3.add(x)
doAssert s1 == @[2, 1, 4]
doAssert s2 == @[7, 2, 1, 4]
doAssert s3 == @['h', 'd']
iterator filterFalse[T](s: openArray[T]; f: proc (a: T): bool): T
Iterator which filters the container s and yields only the elements for which f is false.

Example:

let
  a = @[1, 3, 7, 2, 1, 4]
  b = ['a', 'd', 'h', 'd']
var
  s1: seq[int] = @[]
  s2: seq[int] = @[]
  s3: seq[char] = @[]
proc myodd(a: int): bool = a mod 2 == 1
proc mysmall(a: int): bool = a < 7
proc mysmall(a: char): bool = a < 'h'

for x in filterFalse(a, myodd):
  s1.add(x)
for x in filterFalse(a, mysmall):
  s2.add(x)
for x in filterFalse(b, mysmall):
  s3.add(x)
doAssert s1 == @[2, 4]
doAssert s2 == @[7]
doAssert s3 == @['h']
iterator groupBy[T](s: openArray[T]): tuple[k: T, v: seq[T]]
Iterator which groups the same elements together, yielding a tuple (key, group).

Example:

let
  a = @[1, 2, 5, 2, 7, 5, 1, 2]
  b = ['a', 'b', 'b', 'a', 'b', 'a', 'n', 'd']
var s1: seq[tuple[k: int, v: seq[int]]] = @[]
var s2: seq[tuple[k: char, v: seq[char]]] = @[]

for x in groupBy(a):
  s1.add(x)
for x in groupBy(b):
  s2.add(x)

import algorithm
doAssert s1.sortedByIt(it.k) == @[(k: 1, v: @[1, 1]), (k: 2, v: @[2, 2, 2]),
                                  (k: 5, v: @[5, 5]), (k: 7, v: @[7])]
doAssert s2.sortedByIt(it.k) == @[
  (k: 'a', v: @['a', 'a', 'a']), (k: 'b', v: @['b', 'b', 'b']),
  (k: 'd', v: @['d']), (k: 'n', v: @['n'])]
iterator groupBy[T, U](s: openArray[T]; f: proc (a: T): U): tuple[k: U,
    v: seq[T]]
Iterator which groups the elements based on applying a procedure f on each element, yielding a tuple (key, group).

Example:

let
  a = @[1, 2, 5, 2, 7, 5, 1, 2]
  b = ['a', 'b', 'b', 'a', 'b', 'a', 'n', 'd']
  c = ["ac", "dc", "who", "cream", "clash"]
proc isOdd(x: int): bool = x mod 2 == 1
proc isA(x: char): bool = x == 'a'
proc length(x: string): int = x.len

var s1: seq[tuple[k: bool, v: seq[int]]] = @[]
var s2: seq[tuple[k: bool, v: seq[char]]] = @[]
var s3: seq[tuple[k: int, v: seq[string]]] = @[]

for x in groupBy(a, isOdd):
  s1.add(x)
for x in groupBy(b, isA):
  s2.add(x)
for x in groupBy(c, length):
  s3.add(x)

import algorithm
doAssert s1.sortedByIt(it.k) == @[(k: false, v: @[2, 2, 2]),
                                  (k: true, v: @[1, 5, 7, 5, 1])]
doAssert s2.sortedByIt(it.k) == @[(k: false, v: @['b', 'b', 'b', 'n', 'd']),
                                  (k: true, v: @['a', 'a', 'a'])]
doAssert s3.sortedByIt(it.k) == @[(k: 2, v: @["ac", "dc"]), (k: 3, v: @["who"]),
                                  (k: 5, v: @["cream", "clash"])]
iterator groupConsecutive[T](s: openArray[T]): tuple[k: T, v: seq[T]]
Iterator which groups the same consecutive elements together, yielding a tuple (key, group).

Example:

let
  a = [1, 1, 1, 2, 3, 3, 1, 2, 2, 2]
  b = "abcaabccc"
var
  s1: seq[tuple[k: int, v: seq[int]]] = @[]
  s2: seq[tuple[k: char, v: seq[char]]] = @[]

for x in groupConsecutive(a):
  s1.add(x)
for x in groupConsecutive(b):
  s2.add(x)

doAssert s1 == @[(1, @[1, 1, 1]), (2, @[2]), (3, @[3, 3]), (1, @[1]), (2, @[2, 2, 2])]
doAssert s2 == @[('a', @['a']), ('b', @['b']), ('c', @['c']),
                 ('a', @['a', 'a']), ('b', @['b']), ('c', @['c', 'c', 'c'])]
iterator groupConsecutive[T, U](s: openArray[T]; f: proc (a: T): U): tuple[k: U,
    v: seq[T]]
Iterator which groups the consecutive elements based on applying a procedure f on each element, yielding a tuple (key, group).

Example:

let
  a = @[1, 3, 5, 2, 1, 3, 5, 2, 4, 6, 1]
  b = ['a', 'b', 'c', 'a', 'b', 'a', 'a', 'd']
  c = ["ac", "dc", "dylan", "who", "cream", "clash"]
proc isOdd(x: int): bool = x mod 2 == 1
proc isA(x: char): bool = x == 'a'
proc length(x: string): int = x.len

var s1: seq[tuple[k: bool, v: seq[int]]] = @[]
var s2: seq[tuple[k: bool, v: seq[char]]] = @[]
var s3: seq[tuple[k: int, v: seq[string]]] = @[]

for x in groupConsecutive(a, isOdd):
  s1.add(x)
for x in groupConsecutive(b, isA):
  s2.add(x)
for x in groupConsecutive(c, length):
  s3.add(x)

doAssert s1 == @[(true, @[1, 3, 5]), (false, @[2]), (true, @[1, 3, 5]),
                 (false, @[2, 4, 6]), (true, @[1])]
doAssert s2 == @[(true, @['a']), (false, @['b', 'c']), (true, @['a']),
                 (false, @['b']), (true, @['a', 'a']), (false, @['d'])]
doAssert s3 == @[(2, @["ac", "dc"]), (5, @["dylan"]), (3, @["who"]),
                 (5, @["cream", "clash"])]
iterator islice[T](s: openArray[T]; start = 0; stop = -1; step = 1): T
Iterator which yields elements of s, starting from start (default = 0), until stop (default = -1, go to the end), with step-size step (default = 1).

Example:

let
  a = @[1, 4, 3, 2, 5, 8, 6, 7, 9]
  b = [3.3, 4.4, 9.9, 6.6, 2.2]
var
  s1: seq[int] = @[]
  s2: seq[int] = @[]
  s3: seq[float] = @[]
  s4: seq[float] = @[]
for x in islice(a, 3):
  s1.add(x)
for x in islice(a, 3, 5):
  s2.add(x)
for x in islice(b, step = 2):
  s3.add(x)
for x in islice(b, 1, 99, 2):
  s4.add(x)
doAssert s1 == @[2, 5, 8, 6, 7, 9]
doAssert s2 == @[2, 5, 8]
doAssert s3 == @[3.3, 9.9, 2.2]
doAssert s4 == @[4.4, 6.6]
iterator takeWhile[T](s: openArray[T]; f: proc (a: T): bool): T
Iterator which yields elements of s as long as predicate is true.

Example:

let
  a = @[1, 3, 5, 2, 7, 9]
  b = ['a', 'c', 'd', 'c']
var
  s1: seq[int] = @[]
  s2: seq[int] = @[]
  s3: seq[char] = @[]
proc myodd(a: int): bool = a mod 2 == 1
proc mysmall(a: int): bool = a < 5
proc mysmall(a: char): bool = a < 'd'

for x in takeWhile(a, myodd):
  s1.add(x)
for x in takeWhile(a, mysmall):
  s2.add(x)
for x in takeWhile(b, mysmall):
  s3.add(x)
doAssert s1 == @[1, 3, 5]
doAssert s2 == @[1, 3]
doAssert s3 == @['a', 'c']
iterator product[T](s: openArray[T]; repeat: Positive): seq[T]
Iterator yielding Cartesian products of s with itself, repeat number of times.

Example:

let
  a = [0, 1]
  b = "abc"
var
  s1: seq[seq[int]] = @[]
  s2: seq[seq[char]] = @[]
for x in product(a, 3):
  s1.add(x)
for x in product(b, 2):
  s2.add(x)
doAssert s1 == @[@[0, 0, 0], @[0, 0, 1], @[0, 1, 0], @[0, 1, 1],
                 @[1, 0, 0], @[1, 0, 1], @[1, 1, 0], @[1, 1, 1]]
doAssert s2 == @[@['a', 'a'], @['a', 'b'], @['a', 'c'], @['b', 'a'],
                 @['b', 'b'], @['b', 'c'], @['c', 'a'], @['c', 'b'],
                 @['c', 'c']]
iterator product[T, U](s1: openArray[T]; s2: openArray[U]): tuple[a: T, b: U]
Iterator producing tuples with Cartesian product of the arguments. Equivalent to nested for-loops.

Example:

let
  a = @[1, 2, 3]
  b = "ab"
var s: seq[tuple[a: int, b: char]] = @[]
for x in product(a, b):
  s.add(x)
doAssert s == @[(a: 1, b: 'a'), (a: 1, b: 'b'), (a: 2, b: 'a'),
                (a: 2, b: 'b'), (a: 3, b: 'a'), (a: 3, b: 'b')]
iterator product[T, U, V](s1: openArray[T]; s2: openArray[U]; s3: openArray[V]): tuple[
    a: T, b: U, c: V]
Iterator producing tuples with Cartesian product of the arguments. Equivalent to nested for-loops.

Example:

let
  a = @[1, 2]
  b = "ab"
  c = [9.9, 7.2]
var s: seq[tuple[a: int, b: char, c: float]] = @[]
for x in product(a, b, c):
  s.add(x)
doAssert s == @[(a: 1, b: 'a', c: 9.9), (a: 1, b: 'a', c: 7.2),
                (a: 1, b: 'b', c: 9.9), (a: 1, b: 'b', c: 7.2),
                (a: 2, b: 'a', c: 9.9), (a: 2, b: 'a', c: 7.2),
                (a: 2, b: 'b', c: 9.9), (a: 2, b: 'b', c: 7.2)]
iterator product[T, U, V, W](s1: openArray[T]; s2: openArray[U];
                             s3: openArray[V]; s4: openArray[W]): tuple[a: T,
    b: U, c: V, d: W]
Iterator producing tuples with Cartesian product of the arguments. Equivalent to nested for-loops.

Example:

let
  a = @[1, 2]
  b = "a"
  c = [9.9]
  d = "xyz"
var s: seq[tuple[a: int, b: char, c: float, d: char]] = @[]
for x in product(a, b, c, d):
  s.add(x)
doAssert s == @[(a: 1, b: 'a', c: 9.9, d: 'x'), (a: 1, b: 'a', c: 9.9, d: 'y'),
                (a: 1, b: 'a', c: 9.9, d: 'z'), (a: 2, b: 'a', c: 9.9, d: 'x'),
                (a: 2, b: 'a', c: 9.9, d: 'y'), (a: 2, b: 'a', c: 9.9, d: 'z')]
iterator distinctPermutations[T](s: openArray[T]): seq[T]

Iterator which yields distinct permutations of s.

Permutations are yielded in lexicographical order, without duplicates. If you want to include duplicates, use permutations.

Example:

let
  a = "bab"
  b = "zzz"
var
  s1: seq[seq[char]] = @[]
  s2: seq[seq[char]] = @[]
for x in distinctPermutations(a):
  s1.add(x)
for x in distinctPermutations(b):
  s2.add(x)
doAssert s1 == @[@['a', 'b', 'b'], @['b', 'a', 'b'], @['b', 'b', 'a']]
doAssert s2 == @[@['z', 'z', 'z']]
iterator permutations[T](s: openArray[T]): seq[T]

Iterator which yields all (number of permutations = (s.len)!) permutations of s.

If s contains duplicate elements, some permutations will be the same. If you want permutations without duplicates, use distinctPermutations.

Example:

let
  a = "bab"
  b = "zzz"
var
  s1: seq[seq[char]] = @[]
  s2: seq[seq[char]] = @[]
for x in permutations(a):
  s1.add(x)
for x in permutations(b):
  s2.add(x)
doAssert s1 == @[@['b', 'a', 'b'], @['b', 'b', 'a'], @['a', 'b', 'b'],
                 @['a', 'b', 'b'], @['b', 'b', 'a'], @['b', 'a', 'b']]
doAssert s2 == @[@['z', 'z', 'z'], @['z', 'z', 'z'], @['z', 'z', 'z'],
                 @['z', 'z', 'z'], @['z', 'z', 'z'], @['z', 'z', 'z']]
iterator combinations(n, r: Positive): seq[int] {...}{.raises: [], tags: [].}

Iterator which yields combinations of size r of a range 0 ..< n.

Both arguments must be positive numbers.

Example:

let
  a = 5
  b = 4
  c = 2
var
  s1: seq[seq[int]] = @[]
  s2: seq[seq[int]] = @[]
for x in combinations(a, b):
  s1.add(x)
for x in combinations(a, c):
  s2.add(x)
doAssert s1 == @[@[0, 1, 2, 3], @[0, 1, 2, 4], @[0, 1, 3, 4],
                 @[0, 2, 3, 4], @[1, 2, 3, 4]]
doAssert s2 == @[@[0, 1], @[0, 2], @[0, 3], @[0, 4],
                 @[1, 2], @[1, 3], @[1, 4], @[2, 3], @[2, 4], @[3, 4]]
iterator combinations[T](s: openArray[T]; r: Positive): seq[T]

Iterator which yields combinations of s of length r.

Length r must be a positive number.

Example:

let
  a = "98765"
  b = 4
  c = 2
var
  s1: seq[seq[char]] = @[]
  s2: seq[seq[char]] = @[]
for x in combinations(a, b):
  s1.add(x)
for x in combinations(a, c):
  s2.add(x)
doAssert s1 == @[@['9', '8', '7', '6'], @['9', '8', '7', '5'],
                 @['9', '8', '6', '5'], @['9', '7', '6', '5'],
                 @['8', '7', '6', '5']]
doAssert s2 == @[@['9', '8'], @['9', '7'], @['9', '6'], @['9', '5'],
                 @['8', '7'], @['8', '6'], @['8', '5'], @['7', '6'],
                 @['7', '5'], @['6', '5']]
iterator chunked[T](s: openArray[T]; size: Positive): seq[T]
Iterator which yields size-sized chunks from s.

Example:

let
  a = "abcde"
  b = [11, 12, 13, 14, 15, 16, 17, 18]
var
  s1: seq[seq[char]] = @[]
  s2: seq[seq[int]] = @[]
for x in chunked(a, 2):
  s1.add(x)
for x in chunked(b, 3):
  s2.add(x)
doAssert s1 == @[@['a', 'b'], @['c', 'd'], @['e']]
doAssert s2 == @[@[11, 12, 13], @[14, 15, 16], @[17, 18]]
iterator windowed[T](s: openArray[T]; size: Positive): seq[T]
Iterator which yields size-sized moving window from s.

Example:

let
  a = "abcde"
  b = [11, 12, 13, 14, 15, 16]
var
  s1: seq[seq[char]] = @[]
  s2: seq[seq[int]] = @[]
for x in windowed(a, 2):
  s1.add(x)
for x in windowed(b, 3):
  s2.add(x)
doAssert s1 == @[@['a', 'b'], @['b', 'c'], @['c', 'd'], @['d', 'e']]
doAssert s2 == @[@[11, 12, 13], @[12, 13, 14], @[13, 14, 15], @[14, 15, 16]]
iterator pairwise[T](s: openArray[T]): seq[T]
Convenience wrapper. The same as windowed(s, 2).
iterator unique[T](s: openArray[T]): T
Iterator which yields unique members of s, keeping the original order.

Example:

let
  a = "baobab"
  b = [3, 4, 3, 3, 3, 4, 3, 3]
var
  s1: seq[char] = @[]
  s2: seq[int] = @[]
for x in unique(a):
  s1.add(x)
for x in unique(b):
  s2.add(x)
doAssert s1 == @['b', 'a', 'o']
doAssert s2 == @[3, 4]