rubyguides

Array#tally

Counts occurrences of each element in the array and returns a hash where keys are elements and values are their respective counts.

Use Array#tally when you need a frequency map from values that already appear in a list. It gives you a compact summary without writing a manual counter loop, and it preserves each distinct element as the key in the returned hash.

Basic Usage

fruits = ["apple", "banana", "apple", "orange", "banana", "apple"]

fruits.tally
# => {"apple"=>3, "banana"=>2, "orange"=>1}

The basic call turns a flat array into a frequency map in a single line of code. That is already useful for quick summaries, but sometimes the elements themselves are not what you want to count. When you need to count by a derived property instead of the raw values, the block form gives you that extra layer of control without changing the method signature.

with a block

You can pass a block to tally to count based on the block’s return value:

words = ["hello", "world", "ruby", "hello", "world", "world"]

words.tally { |word| word.length }
# => {5=>2, 6=>1, 4=>3}

This counts how many words have each length.

The block form is useful when the raw values are not the thing you want to count. You can count by length, category, status, or any other derived value. That keeps the frequency logic close to the transformation rule.

tally vs count

These methods are related but serve different purposes:

  • count counts elements that match a given condition and returns an integer
  • tally counts all elements and returns a hash
numbers = [1, 2, 2, 3, 3, 3]

# count - how many elements equal 2?
numbers.count(2)
# => 2

# tally - how many of each element?
numbers.tally
# => {1=>1, 2=>2, 3=>3}

Choose count when you only need one answer, such as how many values equal 2. Choose tally when you want the whole distribution. The return value tells the difference: one integer versus a hash of counts.

tally vs group by

Both return hashes but with different values:

  • group_by groups elements into arrays
  • tally groups elements into integer counts
words = ["hello", "world", "ruby", "hello"]

# group_by - elements grouped into arrays
words.group_by(&:itself)
# => {"hello"=>["hello", "hello"], "world"=>["world"], "ruby"=>["ruby"]}

# tally - elements counted into integers
words.tally
# => {"hello"=>2, "world"=>1, "ruby"=>1}

group_by keeps the original values, while tally keeps only the number of times each value appeared. That makes tally smaller and easier to display when counts are all you need.

Practical Examples

word frequency

Count how often each word appears in a text:

text = "the quick brown fox jumps over the lazy dog the fox"

text.split.tally
# => {"the"=>2, "quick"=>1, "brown"=>1, "fox"=>2, "jumps"=>1, "over"=>1, "lazy"=>1, "dog"=>1}

Word frequency is a classic use for tally because the input is already a flat list of tokens and the output is exactly what you would draw on a whiteboard: each unique word with a count next to it. The pattern extends naturally to any domain where you have repeated identifiers, such as votes, inventory items, or survey responses. In each case, tally does the same job—it summarizes repetition into a hash you can inspect or pass to the next step.

vote counting

votes = ["Alice", "Bob", "Alice", "Charlie", "Bob", "Alice", "Bob", "Bob"]

votes.tally
# => {"Alice"=>3, "Bob"=>4, "Charlie"=>1}

winner = votes.tally.max_by { |name, count| count }
# => ["Bob", 4]

Vote tallying is a natural extension of word counting: the same method call summarizes any repeated identifier into a frequency map. Chaining max_by on the result gives you the winner in one expression, which is cleaner than storing intermediate counts and comparing them manually.

inventory count

inventory = ["sword", "potion", "shield", "sword", "sword", "potion", "ring"]

inventory.tally
# => {"sword"=>3, "potion"=>2, "shield"=>1, "ring"=>1}

Inventory counting works the same way: every repeated item name becomes a count in the output hash. This pattern is especially useful in game or e-commerce logic where the input is a flat list of item identifiers and the output needs to show how many of each are held.

character occurrences

"hello world".chars.tally
# => {"h"=>1, "e"=>1, "l"=>3, "o"=>2, " "=>1, "w"=>1, "r"=>1, "d"=>1}

Performance

Array#tally has O(n) time complexity, where n is the number of elements in the array. It makes a single pass through the array, updating a hash counter for each element.

That single pass makes it a good default for arrays that are already in memory. For very large streams, use the Enumerable version or update a counter as values arrive so you do not need to hold the whole input at once.

See Also