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
| Name | Type | Required | Description |
|---|---|---|---|
n | Integer | yes | The length of each tuple. Convertible to integer per Ruby’s Integer() rules. |
Return value
- Block form: returns the receiver
self. Each yieldedpermutationis a freshArray, independent of the receiver and of every other tuple. - No block: returns a sized
Enumerator. Calling.sizeon it returnsarr.size ** nwithout 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 0–9 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_aand then.sort. - Cardinality explodes.
arr.size ** ngrows without bound. A 5-element alphabet atn = 10produces nearly 10 million tuples, and you’ll be waiting and using memory. Prefer the block form or the lazyEnumeratorform over.to_afor 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_combinationdiscards order, so[a, b]and[b, a]count once.repeated_permutationcounts them separately. For a 3-element source withn = 2,repeated_combinationproduces 6 tuples andrepeated_permutationproduces 9. - Negative
nis a silent no-op. If your code expects a fixed number of yields, that expectation will be wrong for negative input. 0is 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.