gintro/tests/combinatorics.nim
2020-02-20 21:04:50 +01:00

188 lines
4.7 KiB
Nim

# https://forum.nim-lang.org/t/5937
#
# Nimrod Combinatorics Module
# (c) Copyright 2014 Reimer Behrends
#
# See the file "LICENSE", included in this
# distribution, for details about the copyright.
#
## The combinatorics module contains routines to enumerate permutations,
## choices, or combinations of a sequence or array. These routines are
## implemented as iterators that yield sequences. ``sequtils.toSeq``
## can be used to gather all results in a single sequence.
##
## The implementations assume that the input does not contain duplicates;
## if it contains duplicate entries, these will be treated as distinct
## items.
iterator permutations*[T](s: openarray[T]): seq[T] =
## Enumerates all possible permutations of `s`. Example:
##
## .. code-block:: nimrod
## for p in permutations(@[1,2,3]):
##
## ...generates this output:
##
## .. code-block::
## @[1, 2, 3]
## @[2, 1, 3]
## @[1, 3, 2]
## @[2, 3, 1]
## @[3, 2, 1]
## @[3, 1, 2]
let n = len(s)
var pos = newSeq[int](n)
var current = newSeq[T](n)
for i in 0..n-1:
pos[i] = i
while true:
for i in 0..n-1:
current[i] = current[pos[i]]
current[pos[i]] = s[i]
yield current
var i = 1
while i < n:
pos[i] -= 1
if pos[i] < 0:
pos[i] = i
i += 1
else:
break
if i >= n:
break
iterator combinations*[T](s: openarray[T], k: int): seq[T] =
## Enumerates all combinations of `k` distinct elements
## out of `s`. Example:
##
## .. code-block:: nimrod
## for p in combinations(@[1,2,3], 2):
##
## ...generates this output:
##
## .. code-block::
## @[1, 2]
## @[1, 3]
## @[2, 3]
let n = len(s)
assert k >= 0 and k <= n
var pos = newSeq[int](k)
var current = newSeq[T](k)
for i in 0..k-1:
pos[k-i-1] = i
var done = false
while not done:
for i in 0..k-1:
current[i] = s[pos[k-i-1]]
yield current
var i = 0
while i < k:
pos[i] += 1
if pos[i] < n-i:
for j in 0..i-1:
pos[j] = pos[i] + i - j
break
i += 1
if i >= k:
break
iterator multicombinations*[T](s: openarray[T], k: int): seq[T] =
## Enumerates all ordered combinations of `k` elements
## out of `s` where the elements can be repeated.
## Example:
##
## .. code-block:: nimrod
## for p in multicombinations(@[1,2,3], 2):
##
## ...generates this output:
##
## .. code-block::
## @[1, 1]
## @[1, 2]
## @[1, 3]
## @[2, 2]
## @[2, 3]
## @[3, 3]
let n = len(s)
assert k >= 0 and k <= n
var pos = newSeq[int](k)
var current = newSeq[T](k)
var done = false
while not done:
for i in 0..k-1:
current[i] = s[pos[k-i-1]]
yield current
var i = 0
while i < k:
pos[i] += 1
if pos[i] < n:
for j in 0..i-1:
pos[j] = pos[i]
break
i += 1
if i == k:
break
iterator tuples*[T](s: openarray[T], k: int): seq[T] =
## Enumerates all ordered combinations of `k` elements
## out of `s` where the elements can be repeated. This is
## essentially the Cartesian product of `s` with itself
## for `k` factors. Example:
##
## .. code-block:: nimrod
## for p in tuples(@[1,2], 2):
##
## ...generates this output:
##
## .. code-block::
## @[1, 1]
## @[1, 2]
## @[2, 1]
## @[2, 2]
if k >= 0 and len(s) > 0:
var pos = newSeq[int](k)
var current = newSeq[T](k)
while true:
for i in 0..k-1:
current[i] = s[pos[i]]
yield current
var i = 0
while i < k:
pos[i] += 1
if pos[i] >= len(s):
pos[i] = 0
i += 1
else:
break
if i == k:
break
when isMainModule:
import sets, math, sequtils
template count(s: untyped): untyped =
block:
var i = 0
for x in s: i += 1
i
proc mcoeff(a, b: int): int =
binom(a+b-1, b)
for x in combinations([1,2,3], 2):
echo x
#doAssert count(permutations[int]([])) == 1
assert toSeq(permutations([1,2,3,4,5])).len == fac(5)
assert toSeq(permutations([1,2,3,4,5])).len == fac(5)
assert toSeq(permutations(["a", "b", "c"])).len == fac(3)
assert toSeq(combinations([1,2,3,4], 1)).len == binom(4, 1)
assert toSeq(combinations([1,2,3,4], 2)).len == binom(4, 2)
assert toSeq(combinations([1,2,3,4], 3)).len == binom(4, 3)
assert toSeq(combinations(["a", "b", "c", "d", "e"], 3)).len == 10
assert toSeq(multicombinations([1,2,3,4], 1)).len == mcoeff(4, 1)
assert toSeq(multicombinations([1,2,3,4], 2)).len == mcoeff(4, 2)
assert toSeq(multicombinations([1,2,3,4], 3)).len == mcoeff(4, 3)
assert toSeq(tuples([1,2,3,4], 3)).len == 4*4*4
assert toSeq(tuples([1,2], 10)).len == 1024