rubyguides

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

NameTypeRequiredDescription
nIntegeryesThe 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 == 0 yields exactly one combination: [].
  • n < 0 does not call the block and returns the receiver. No exception is raised.
  • n omitted raises ArgumentError: wrong number of arguments (given 0, expected 1).
  • n non-integer is coerced via Integer(); values that can’t be converted raise TypeError.
  • n > arr.size still 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 [[]] for n == 0 and nothing for any n > 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:

  1. Prefer the block form or the lazy Enumerator form. Avoid .to_a unless you actually want every combination in memory.
  2. If you only need a count, use count directly on the enumerator. It iterates without materializing, but it still walks the whole sequence.

See Also