Array#repeated_combination: Multiset Combinations in Ruby
arr.repeated_combination(n) { |c| block } Array#repeated_combination(n) enumerates every combination of length n that can be drawn from the receiver, with elements allowed to repeat. It’s the combinations-with-repetition sibling of Array#combination (no repeats), the unordered counterpart of Array#repeated_permutation, and a member of the Enumerable family of iteration methods on Ruby arrays.
You reach for it when the order of selection doesn’t matter but the same element may appear more than once. Two dice showing 3 and 4 is the same outcome as 4 and 3, but a pair of 3s is a separate, valid outcome. That’s the territory repeated_combination covers.
Syntax
arr.repeated_combination(n) { |combination| ... } # => arr
arr.repeated_combination(n) # => Enumerator
With a block, the method yields each combination as a freshly allocated Array and returns the receiver. Without a block, it returns a lazy Enumerator and does no work until you iterate it.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
n | Integer | yes | The length of each combination. Convertible to integer per Ruby’s Integer() rules. |
Examples
Basic usage
[1, 2, 3].repeated_combination(2).to_a
# => [[1, 1], [1, 2], [1, 3], [2, 2], [2, 3], [3, 3]]
Six combinations of length 2 from a 3-element source. The count matches the formula C(k + n - 1, n) with k = 3 and n = 2: C(4, 2) = 6.
With a block
[1, 2, 3].repeated_combination(2) { |c| puts c.inspect }
# [1, 1]
# [1, 2]
# [1, 3]
# [2, 2]
# [2, 3]
# [3, 3]
The block form is usually what you want for any non-trivial input. It avoids materializing the full result set, and the returned receiver means you can chain it with other array methods.
arr = [1, 2]
result = arr.repeated_combination(1) { |c| c }
result.equal?(arr) # => true
result # => [1, 2]
This confirms that the block form hands back the same array object. The return value is what makes the method chain-friendly with other Enumerable methods.
Practical examples
When two dice are rolled, the order of the values doesn’t matter but doubles count as a separate outcome. The block form yields each unordered pair exactly once, with the same value selectable on both positions:
dice = [1, 2, 3, 4, 5, 6]
outcomes = dice.repeated_combination(2).to_a
outcomes.length
# => 21
outcomes.first(3)
# => [[1, 1], [1, 2], [1, 3]]
Twenty-one ways to roll two dice when order doesn’t matter and doubles count. C(7, 2) = 21.
Exhaustive multiset search over a small alphabet:
alphabet = [0, 1, 2, 3]
alphabet.repeated_combination(3).to_a.length
# => 20
That’s C(6, 3) = 20 multisets of length 3. If you also need ordering, use repeated_permutation(3) instead, which is one of the permutations-based methods and produces 4 ** 3 = 64 ordered tuples.
Common Patterns
The classic accumulator pattern works well here. Build a hash of multiset counts without materializing the full sequence:
tally = Hash.new(0)
[:a, :b, :c].repeated_combination(2) { |pair| tally[pair] += 1 }
tally
# => {[:a, :a]=>1, [:a, :b]=>1, [:a, :c]=>1, [:b, :b]=>1, [:b, :c]=>1, [:c, :c]=>1}
This stays lazy and lets you stop early with Enumerator#first, take, or find.
Edge Cases
n == 0yields exactly one combination:[].n < 0does not call the block and returns the receiver. No exception is raised.nomitted raisesArgumentError: wrong number of arguments (given 0, expected 1).nnon-integer is coerced viaInteger(); values that can’t be converted raiseTypeError.n > arr.sizestill yields combinations, which is the whole point. The source has fewer elements than the target length, and repeats fill the gap.- An empty receiver yields
[[]]forn == 0and nothing for anyn > 0. - No block returns an
Enumerator, so combinations are computed only on iteration.
The Ruby docs note that the order of yielded combinations is indeterminate. In practice it’s stable across runs on a given Ruby build, but don’t write code that depends on a specific sequence.
Watch the count grow
The count grows fast. For a 10-element source and n = 20, you get C(29, 20) = 10,015,005 combinations. Five elements at n = 30 is small (46,376), but push those numbers up and the result set explodes.
Two habits help:
- Prefer the block form or the lazy
Enumeratorform. Avoid.to_aunless you actually want every combination in memory. - If you only need a count, use
countdirectly on the enumerator. It iterates without materializing, but it still walks the whole sequence.