Enumerable#zip
The zip method combines elements from the caller with corresponding elements from one or more other enumerables. It returns a new array of arrays, where each inner array contains one element from each collection at the same index.
zip is defined on Enumerable, so it works on any collection that implements the enumerable protocol — arrays, hashes, ranges, and custom enumerables.
How It Works
zip takes one or more collections as arguments. It iterates in parallel over the caller and all arguments, grouping elements at the same position into a sub-array:
collection.zip(*others)
Basic Usage
Combine two arrays element-by-element:
numbers = [1, 2, 3]
letters = ['a', 'b', 'c']
numbers.zip(letters)
# => [[1, "a"], [2, "b"], [3, "c"]]
Handling Different-Length Enumerables
When the collections have different lengths, zip stops at the shortest one. The extra elements from longer collections are discarded, and shorter ones get nil placeholders:
short = [1, 2]
long = ['a', 'b', 'c', 'd']
short.zip(long)
# => [[1, "a"], [2, "b"]]
# Reversing the order changes which elements survive
long.zip(short)
# => [["a", 1], ["b", 2], ["c", nil], ["d", nil]]
To fill in a value other than nil, use cycle or pad the shorter array first:
a = [1, 2, 3]
b = ['a', 'b']
# Pad b with a placeholder
padded_b = b + [0] * (a.length - b.length)
a.zip(padded_b)
# => [[1, "a"], [2, "b"], [3, 0]]
With Multiple Enumerables
You can zip together more than two collections:
numbers = [1, 2, 3]
letters = ['a', 'b', 'c']
symbols = [:x, :y, :z]
numbers.zip(letters, symbols)
# => [[1, "a", :x], [2, "b", :y], [3, "c", :z]]
Converting to a Hash
A common pattern is turning two parallel arrays into a hash using Hash[]:
keys = [:name, :age, :city]
values = ['Alice', 30, 'Boston']
Hash[keys.zip(values)]
# => {:name => "Alice", :age => 30, :city => "Boston"}
The to_h method on the result works equally well:
keys.zip(values).to_h
# => {:name => "Alice", :age => 30, :city => "Boston"}
Using with Hashes
When you zip a hash, only its values are used (keys are not treated as the enumerable content):
config = { theme: 'dark', font_size: 14 }
overrides = ['light', 16]
config.values.zip(overrides)
# => [["dark", "light"], [14, 16]]
To zip hash keys with an array, call .keys or .to_a explicitly:
hash = { a: 1, b: 2, c: 3 }
keys = [:x, :y, :z]
hash.keys.zip(keys)
# => [[:a, :x], [:b, :y], [:c, :z]]
With a Block
Pass a block to iterate over the zipped pairs without building an intermediate array:
[1, 2, 3].zip(['a', 'b', 'c']) do |num, letter|
puts "#{num} -> #{letter}"
end
# Output:
# 1 -> a
# 2 -> b
# 3 -> c
When called with a block, zip returns nil.
Relationship to Array#transpose
zip and Array#transpose both rearrange elements into nested arrays, but they work differently:
transposeoperates on an array of arrays and swaps rows and columns. It requires a rectangular structure.zippairs elements from multiple arrays at matching indices. It handles unequal lengths naturally.
# transpose swaps rows and columns
matrix = [[1, 2], ['a', 'b'], [:x, :y]]
matrix.transpose
# => [[1, "a", :x], [2, "b", :y]]
# zip combines arrays element-by-element
[1, 2, 3].zip(['a', 'b', 'c'])
# => [[1, "a"], [2, "b"], [3, "c"]]
For non-rectangular data, zip is more predictable since it stops at the shortest collection rather than raising an error.
Parameters
| Parameter | Type | Description |
|---|---|---|
*others | Enumerable | One or more collections to zip with self |
Return Value
Returns a new array of arrays. Each inner array contains elements at the same index from the caller and all arguments. Returns an empty array if the caller is empty.
See Also
- Enumerable#flat_map — transform and flatten in one step
- Enumerable#tally — count occurrences of elements