rubyguides

Array#repeated_permutation

arr.repeated_permutation(n) { |p| block }

Array#repeated_permutation(n) enumerates every ordered tuple of length n drawn from the receiver with replacement. The same element can appear in multiple positions of a single tuple, and [a, b] counts as a different tuple from [b, a]. The number of tuples produced is arr.size ** n. It’s the ordered-with-replacement sibling of Array#permutation and the ordered counterpart of Array#repeated_combination, completing the quartet of “arrange the elements” methods on Ruby arrays.

The method is most useful when you need to enumerate a Cartesian space over a small source: PIN candidates, brute-force short passwords, exhaustive search over a tiny alphabet, all without writing the nested loops by hand.

Syntax

arr.repeated_permutation(n) { |permutation| ... }   # => arr
arr.repeated_permutation(n)                         # => Enumerator

With a block, the method yields each tuple as a freshly allocated Array and returns the receiver. Without a block, it returns a sized Enumerator and does no work until you iterate it.

Parameters

NameTypeRequiredDescription
nIntegeryesThe length of each tuple. Convertible to integer per Ruby’s Integer() rules.

Return value

  • Block form: returns the receiver self. Each yielded permutation is a fresh Array, independent of the receiver and of every other tuple.
  • No block: returns a sized Enumerator. Calling .size on it returns arr.size ** n without iterating, which is handy when you only need a count.

Examples

Basic usage, size 1

result = []
[0, 1, 2].repeated_permutation(1) { |p| result << p }
result
# => [[0], [1], [2]]

Three tuples, one per element of the source, each wrapped in its own one-element array. The receiver comes back unchanged after the block runs to completion.

Basic usage, size 2

[0, 1, 2].repeated_permutation(2).to_a
# => [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2], [2, 0], [2, 1], [2, 2]]

Nine tuples, matching the formula 3 ** 2 = 9. Order matters, so both [0, 1] and [1, 0] appear as separate entries in the result. This is the defining difference between permutation and combination on the same receiver.

Enumerator form

enum = [0, 1, 2].repeated_permutation(2)
enum.class
# => Enumerator
enum.size
# => 9

Because the enumerator is sized, enum.size returns the count without iterating the sequence. That’s a cheap way to confirm the cardinality before you materialize the result.

Block form returns the receiver

arr = [1, 2, 3]
ret = arr.repeated_permutation(2) { |p| nil }
ret.equal?(arr)
# => true

The block form hands back the same array object every time. The C source return ary; at the end of rb_ary_repeated_permutation makes that explicit.

Edge cases: n = 0 and n < 0

[0, 1, 2].repeated_permutation(0) { |p| p }
# => []   # block yielded exactly once, with []

[0, 1, 2].repeated_permutation(-1) { fail "unreachable" }
# => [0, 1, 2]   # block never called; receiver returned

A length of 0 yields a single empty array. A negative n is a silent no-op: the block is never called, no exception is raised, and the receiver comes back unchanged.

Realistic use: 4-digit PIN candidates

A 4-digit PIN from digits 09 is exactly a repeated permutation of length 4 from a 10-element source:

digits = (0..9).to_a
pins   = digits.repeated_permutation(4).to_a
pins.size
# => 10000
pins.first(3)
# => [[0, 0, 0, 0], [0, 0, 0, 1], [0, 0, 0, 2]]

10 ** 4 = 10_000, which matches the full PIN space for 4-digit numeric codes. This is a useful sanity check whenever you generate exhaustive candidate lists, and the block form saves you from materializing the full set if you only need to test each one.

Realistic use: brute-force short passwords

alphabet = %w[a b c]
passwords = alphabet.repeated_permutation(3).map(&:join)
passwords
# => ["aaa", "aab", "aac", "aba", "abb", "abc", "aca", "acb", "acc",
#     "baa", "bab", "bac", "bba", "bbb", "bbc", "bca", "bcb", "bcc",
#     "caa", "cab", "cac", "cba", "cbb", "cbc", "cca", "ccb", "ccc"]

Twenty-seven candidates, matching 3 ** 3 = 27. This pattern is the right shape for small brute-force searches and exhaustive test-case generation.

Composing with Enumerator#lazy

The enumerator composes with other lazy operations:

[0, 1].repeated_permutation(3).lazy.map(&:join).first(5)
# => ["000", "001", "010", "011", "100"]

.lazy is mostly redundant here (the enumerator is already non-eager), but it composes cleanly inside longer lazy chains.

Gotchas

  • Indeterminate order. The official documentation states that the order of yielded permutations is indeterminate. Don’t write tests or build logic that assumes a particular sequence. If you need sorted output, materialize with .to_a and then .sort.
  • Cardinality explodes. arr.size ** n grows without bound. A 5-element alphabet at n = 10 produces nearly 10 million tuples, and you’ll be waiting and using memory. Prefer the block form or the lazy Enumerator form over .to_a for any non-trivial input.
  • Not the same as Array#permutation. permutation (no “repeated”) does not reuse elements. The “repeated” prefix is doing real work, not decorating the name.
  • Not the same as Array#repeated_combination. repeated_combination discards order, so [a, b] and [b, a] count once. repeated_permutation counts them separately. For a 3-element source with n = 2, repeated_combination produces 6 tuples and repeated_permutation produces 9.
  • Negative n is a silent no-op. If your code expects a fixed number of yields, that expectation will be wrong for negative input.
  • 0 is valid and yields one empty array. Easy to miss if you’ve assumed “size 0 means nothing to do.” It means “I want a single 0-length tuple, please.”
  • Mutating the receiver during iteration is safe. The C implementation snapshots the receiver with ary_make_shared_copy(ary) before iterating, so changes to the original after the call don’t affect the enumeration.

Comparison with Array#product

Array#product and Array#repeated_permutation overlap in the single-array case but behave differently in the multi-array case. With one source array, arr.product(arr) and arr.repeated_permutation(2) produce the same tuples in spirit, but product is designed for the multi-array Cartesian product. repeated_permutation is the generalisation that allows the same element to be reused, which product also allows for distinct input arrays:

[:a, :b].product([:a, :b])
# => [[:a, :a], [:a, :b], [:b, :a], [:b, :b]]

[:a, :b].repeated_permutation(2).to_a
# => [[:a, :a], [:a, :b], [:b, :a], [:b, :b]]

# They differ when the receiver has duplicate values:
[:a, :a, :b].product([:a, :b])
# => [[:a, :a], [:a, :b], [:a, :a], [:a, :b], [:b, :a], [:b, :b]]   # 6 tuples
[:a, :a, :b].repeated_permutation(2).to_a.length
# => 9   # each occurrence of :a is its own slot

Reach for repeated_permutation when you want ordered-with-replacement tuples from a single source. Reach for product when you have multiple distinct input arrays to combine.

See Also