Enumerable#tally

Returns: Hash · Updated March 31, 2026 · Enumerable
ruby enumerable tally stdlib

The tally method counts the occurrences of each element in a collection and returns the results as a hash. Each unique element becomes a key, and its count becomes the corresponding value. This is a straightforward way to analyze the distribution of values in your data.

Basic Usage

Count all elements in an array:

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

The method iterates through the collection once and builds the hash of counts.

With Numbers

dice_rolls = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
dice_rolls.tally
# => {3=>2, 1=>2, 4=>1, 5=>3, 9=>1, 2=>1, 6=>1}

With Ranges

Ranges are enumerables, so tally works on them directly:

(1..6).tally
# => {1=>1, 2=>1, 3=>1, 4=>1, 5=>1, 6=>1}

# Count even vs odd in a range
(1..10).tally { |n| n.even? ? "even" : "odd" }
# => {"odd"=>5, "even"=>5}

The block form transforms each element before counting, giving you flexibility in how you categorize.

Custom Counting with Hash Argument

You can provide an existing hash to tally to customize how counts are accumulated:

votes = ["yes", "no", "yes", "yes", "abstain", "no", "yes"]

# Pre-initialize all expected keys
tally = votes.tally({ "yes" => 0, "no" => 0, "abstain" => 0, "absent" => 0 })
# => {"yes"=>3, "no"=>2, "abstain"=>1, "absent"=>0}

This is useful when you need all possible categories represented in the result, even if some have zero counts.

Use Case: Survey with All Options

survey_responses = ["satisfied", "neutral", "satisfied", "dissatisfied", "satisfied"]

# Ensure all rating levels appear in results
all_options = ["very_dissatisfied", "dissatisfied", "neutral", "satisfied", "very_satisfied"]
tally = survey_responses.tally(Hash.new(0).merge!(all_options.map { |k| [k, 0] }.to_h))

# Or more simply, start with the hash you need:
tally = survey_responses.tally(all_options.product([0]).to_h)
# => {"satisfied"=>3, "neutral"=>1, "dissatisfied"=>1}

Comparison to group_by + count

Before tally, you would often combine group_by with count to achieve similar results:

words = ["cat", "dog", "fish", "cat", "dog", "cat"]

# Old approach with group_by + count
words.group_by(&:itself).transform_values(&:count)
# => {"cat"=>3, "dog"=>2, "fish"=>1}

# Modern approach with tally
words.tally
# => {"cat"=>3, "dog"=>2, "fish"=>1}

The tally method is more concise and performs the same operation in a single pass, making it both cleaner and slightly more efficient.

When group_by Still Shines

group_by remains useful when you need the actual grouped elements rather than just counts:

words = ["cat", "dog", "fish", "cat", "dog", "cat"]

# Get the actual elements grouped
words.group_by(&:itself)
# => {"cat"=>["cat", "cat", "cat"], "dog"=>["dog", "dog"], "fish"=>["fish"]}

# tally only gives you counts, not the elements
words.tally
# => {"cat"=>3, "dog"=>2, "fish"=>1}

With Custom Enumerables

tally works with any object that includes the Enumerable module:

# Via to_enum on a Hash
stock = { apples: 50, bananas: 30, oranges: 25, apples: 20 }
stock.to_enum.tally
# Note: Hash keys are unique, so this counts key occurrences

# Fibonacci as an Enumerable
fib = Enumerator.new do |y|
  a, b = 0, 1
  loop do
    y << a
    a, b = b, a + b
  end
end

fib.take(10).tally
# => {0=>1, 1=>3, 2=>1, 3=>1, 4=>1, 5=>1, 6=>1, 8=>1}

Return Value

tally always returns a hash. The keys are the unique elements from the collection, and the values are integers representing how many times each element appeared.

[].tally
# => {}

[1, 1, 1].tally
# => {1=>3}

Practical Examples

Analyzing Log Files

log_levels = ["INFO", "DEBUG", "INFO", "ERROR", "INFO", "DEBUG", "ERROR", "INFO"]
log_levels.tally
# => {"INFO"=>4, "DEBUG"=>2, "ERROR"=>2}

Word Frequency Analysis

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

Color Distribution in Inventory

inventory = [:red, :blue, :red, :green, :blue, :red, :blue, :blue]
inventory.tally
# => {:red=>3, :blue=>4, :green=>1}

See Also