Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 4

Counting with collections.Counter

Recommended resources

Hello! Welcome to the fourth lesson in our "Python Tools for Competitive Programming" module.

In our last lesson, we explored collections.deque and saw how choosing the right data structure can dramatically improve performance, especially for queue and stack implementations. Today, we'll continue our journey through the collections module by focusing on another incredibly useful tool: collections.Counter.

The goal of this lesson is to learn how to use Counter for frequency counting and related problems. Counting how many times each item appears in a collection is a fundamental task in a vast number of competitive programming challenges, from checking for anagrams to analyzing data. Counter provides a highly efficient and convenient way to do this.

1. The Tedious Way to Count: A Refresher

Before we dive into Counter, let's remember how we would traditionally count items using a standard dictionary. Imagine you have a list of votes and you want to tally them.

votes = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple']
vote_counts = {}

for vote in votes:
    if vote not in vote_counts:
        vote_counts[vote] = 1
    else:
        vote_counts[vote] += 1

print(vote_counts)
# Output: {'apple': 3, 'banana': 2, 'orange': 1}

This works, but it's verbose. You have to handle the case where a key doesn't exist yet. The collections.Counter is designed to make this entire process simpler and more Pythonic.

The following article provides a great overview, starting with these manual methods before introducing Counter as the ideal solution.

Python's Counter: The Pythonic Way to Count Objects

The article "Python's Counter: The Pythonic Way to Count Objects" from Real Python provides a detailed look at Counter. We'll start with the introductory section that builds the case for why Counter is needed.

Read the section titled "Counting Objects in Python". This will reinforce the common patterns for counting with dictionaries and defaultdict, setting the stage for Counter.


2. Introducing collections.Counter

A Counter is a subclass of the dictionary (dict) specifically designed for counting hashable objects. It automatically handles the logic we wrote manually above.

To use it, you first need to import it from the collections module:
from collections import Counter

You can create a Counter object by passing it any iterable (like a list, string, or tuple).

from collections import Counter

# From a list
votes = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple']
vote_counts = Counter(votes)
print(vote_counts)
# Output: Counter({'apple': 3, 'banana': 2, 'orange': 1})

# From a string
char_counts = Counter("mississippi")
print(char_counts)
# Output: Counter({'i': 4, 's': 4, 'p': 2, 'm': 1})

As you can see, Counter instantly gives you a dictionary-like object where keys are the items and values are their frequencies.

Using collections.Counter for Frequency Counting in Python
This image shows how a `Counter` object is created from a list of strings, resulting in a clean frequency map.

For a visual walkthrough of creating and using Counter, the following video is an excellent starting point.

Intermediate Python Tutorial #6 - Collections /Counter()

The video "Intermediate Python Tutorial #6 - Collections /Counter()" by Tech With Tim clearly demonstrates the basics of Counter.

Watch the first three minutes of the video (00:00 - 03:22). This will show you what collections.Counter is and the various ways you can initialize one.


3. Key Counter Methods and Features

Counter behaves like a regular dictionary in many ways, but it comes with some special features that are perfect for algorithmic problems.

Accessing Counts (and the KeyError killer)

You can access the count of an item using standard dictionary key lookup. However, unlike a dictionary, if you try to access an item that doesn't exist, Counter will simply return 0 instead of raising a KeyError. This is extremely convenient and can make your code cleaner and safer.

from collections import Counter

char_counts = Counter("programming")

print(char_counts['r'])      # Output: 2
print(char_counts['z'])      # Output: 0 (No KeyError!)

Finding the Most Common Elements: most_common(n)

This is one of Counter's most powerful methods for competitive programming. most_common(n) returns a list of the n most common elements and their counts, sorted from most common to least common.

from collections import Counter

data = ['a', 'b', 'c', 'a', 'b', 'a', 'd', 'a']
counts = Counter(data)

print(counts.most_common(1))  # Output: [('a', 4)]
print(counts.most_common(2))  # Output: [('a', 4), ('b', 2)]
print(counts.most_common())   # Output: [('a', 4), ('b', 2), ('c', 1), ('d', 1)]

If you don't provide a number, it returns all elements sorted by frequency.

Updating Counts: update()

You can add counts from another iterable or another Counter using the update() method. This adds to existing counts, unlike a dictionary's update() method which overwrites keys.

from collections import Counter

inventory = Counter({'apples': 5, 'oranges': 3})
print(f"Initial inventory: {inventory}")

new_shipment = ['apples', 'apples', 'bananas', 'oranges']
inventory.update(new_shipment)

print(f"Updated inventory: {inventory}")
# Output: Updated inventory: Counter({'apples': 7, 'oranges': 4, 'bananas': 1})

To learn more about these essential methods, let's turn back to the Real Python article.

Python's Counter: The Pythonic Way to Count Objects

This article provides clear examples for the most important Counter methods.

Read the sections titled "Getting Started With Python’s Counter", focusing on "Updating Object Counts," "Accessing the Counter’s Content," and "Finding Most Common Objects."


4. Application: Solving a HackerRank Problem

Now, let's see how Counter can be used to solve a typical problem from a platform like HackerRank. This will demonstrate its practical value in a competitive programming context.

The Problem: A shoe shop owner has a certain number of shoes of various sizes in stock. A list of customers arrives, each with a desired shoe size and the price they are willing to pay. If the shop has the requested shoe size in stock, they sell it and earn the money. Your task is to calculate the total earnings.

This problem is a perfect fit for Counter:

  1. The initial shoe inventory can be stored in a Counter where keys are shoe sizes and values are the quantity in stock.
  2. For each customer, you check if their desired size has a count greater than 0 in your inventory Counter.
  3. If it does, you add their offered price to your total earnings and decrement the count for that shoe size.

The following video provides a complete walkthrough of this exact problem.

29. Collections Counter: Hackerrank | Python Solution Explained

In this video, "Collections Counter: Hackerrank | Python Solution Explained," Dr. Amir Charkhi solves the shoe shop problem step-by-step.

Watch from 04:13 to the end. Pay close attention to how the input is parsed, how the Counter is initialized for the inventory, and how it's checked and updated inside the loop for each customer.

This example shows how Counter simplifies inventory management logic, making the solution concise and efficient.

Test your understanding!

Two strings are anagrams if they contain the same characters with the same frequencies (e.g., "listen" and "silent").

Write a single-line function are_anagrams(s1: str, s2: str) -> bool that uses collections.Counter to determine if two strings are anagrams.

Show answer
from collections import Counter

def are_anagrams(s1: str, s2: str) -> bool:
    return Counter(s1) == Counter(s2)

# Test cases
print(are_anagrams("listen", "silent"))  # Output: True
print(are_anagrams("python", "typhon"))  # Output: True
print(are_anagrams("hello", "world"))    # Output: False

This elegant solution works because Counter objects, like dictionaries, are equal if and only if they have the same key-value pairs. By creating a Counter for each string, we are effectively generating a frequency map of their characters. If the maps are identical, the strings must be anagrams.


5. Advanced Operations

Counter objects also support mathematical operations, treating them like multisets (sets that can contain duplicate elements).

  • Addition (+): Adds counts from two counters.
  • Subtraction (-): Subtracts counts. Note: this keeps only results with positive counts.
  • Intersection (&): Takes the minimum of counts for each common element.
  • Union (|): Takes the maximum of counts for each common element.
from collections import Counter

c1 = Counter(a=3, b=1)
c2 = Counter(a=1, b=2)

print(c1 + c2)       # Addition: Counter({'a': 4, 'b': 3})
print(c1 - c2)       # Subtraction: Counter({'a': 2}) (b is not included as 1-2 < 0)
print(c1 & c2)       # Intersection: Counter({'a': 1, 'b': 1})
print(c1 | c2)       # Union: Counter({'a': 3, 'b': 2})

These operations can be powerful shortcuts in more complex problems involving frequency analysis.


Conclusion

You have now added collections.Counter to your competitive programming toolkit. It is the go-to tool for any problem involving frequency counting, offering simplicity, safety, and performance.

Key Takeaways:

  • Counter is a specialized dictionary for counting hashable objects.
  • It simplifies frequency counting, replacing verbose for loops with a single line of code.
  • Accessing a missing key returns 0 instead of a KeyError.
  • The .most_common(n) method is invaluable for finding the most frequent items.
  • Counter objects can be used to solve a wide range of problems, from inventory management to checking for anagrams.

Up Next: We will continue exploring the collections module by looking at collections.defaultdict. Like Counter, it is another dictionary subclass designed to simplify a common coding pattern: grouping items into collections.