Problem 929

Abstract / Summary

The task is to determine how many different emails result after applying specific local name and domain name rules to a list of email addresses.

Note

The full problem description, including helpful examples, can be found on LeetCode.

Python 0929.py

class Solution:
    def numUniqueEmails(self, emails: List[str]) -> int:
        def filter(mail):
            # regex could be used, too

            # dots
            pos = mail.find(".")
            while pos < mail.find("@"):
                mail = mail[:pos] + mail[pos+1:]
                pos = mail.find(".")

            # plus
            pos = mail.find("+")
            at  = mail.find("@")
            if pos != -1 and pos < at:
                mail = mail[:pos] + mail[at:]

            return mail

        return len(set(filter(m) for m in emails))
20 lines | 585 bytes | submitted October 28, 2023 | GitHub   23% comment ratio | tab size 4 | low complexity

Difficulty

This problem is ranked as
easy medium hard

I solved 663 easy, 1250 medium and 309 hard problems

AI Disclaimer

Every single line of code was written by me, 100% from scratch, between August 2023 and August 2024.
However, two years later I set up this website and used various AI tools to generate most of the walkthrough / explanations below.

See this dedicated page for more details.
LeetCode profile | GitHub repo

Code Walkthrough

Overview

The provided implementation defines a Solution class with a single public method numUniqueEmails. The method receives a list of email address strings and returns the count of distinct addresses after applying the normalization rules defined by the problem statement. Normalization consists of two transformations applied to the local part of each email: removal of all periods (.) and truncation at the first plus sign (+). The domain part (after @) remains unchanged. The code achieves this by defining an inner helper function filter that performs the transformations in place on a copy of the input string, then builds a set of the filtered results and returns its size.

Core Logic

Public Interface

def numUniqueEmails(self, emails: List[str]) -> int:
    return len(set(filter(m) for m in emails))

The method iterates over the input list emails, applies the filter function to each element, and constructs a Python set from the generator expression. Because a set stores only unique elements, its length directly yields the number of distinct normalized email addresses.

Helper Function - filter

def filter(mail):
    # dots
    pos = mail.find(".")
    while pos < mail.find("@"):
        mail = mail[:pos] + mail[pos+1:]
        pos = mail.find(".")

    # plus
    pos = mail.find("+")
    at  = mail.find("@")
    if pos != -1 and pos < at:
        mail = mail[:pos] + mail[at:]

    return mail

The helper function receives a single email string mail. It performs two sequential phases:

  1. Period Removal - The code locates the first period using find. While the found position lies before the @ symbol, the character at that position is omitted by concatenating the substring before the period with the substring after it. The search for the next period restarts from the beginning of the modified string. This loop continues until no period remains in the local part.

  2. Plus Truncation - After all periods are removed, the function searches for a plus sign. If a plus sign exists and appears before the @, the string is sliced to keep everything up to the plus sign and then the domain part (starting at @). The portion after the plus sign is discarded.

The transformed string is returned to the caller.

Edge Cases Handled

The algorithm explicitly checks the relative positions of special characters with respect to the @ delimiter, ensuring that transformations affect only the local part.

When the email contains no periods, the initial while condition fails because mail.find(".") returns -1, which is not less than the index of @. Consequently, the loop body is skipped, leaving the string unchanged.

If a plus sign appears after the @ symbol, the condition pos < at prevents truncation, preserving the domain portion that may legitimately contain a plus sign.

Emails that lack a plus sign altogether cause mail.find("+") to return -1. The subsequent if statement evaluates to false, so the string is returned without truncation.

The code also gracefully handles emails where the local part is empty before the @ (e.g., +@example.com). The plus-truncation step will produce a string consisting solely of the domain, which is a valid normalized representation under the given rules.

When multiple periods appear consecutively, each iteration removes a single period, and the loop continues until none remain, effectively collapsing any sequence of periods.

If the email contains a period immediately before the @ (e.g., user.@example.com), the period is removed because the loop condition checks pos < mail.find("@"). The resulting local part ends with the character preceding the removed period, satisfying the rule that all periods are ignored.

Performance Optimizations

The implementation avoids the overhead of regular expression processing by using simple string search and slicing operations. Direct calls to find and slice concatenation are generally faster than compiling and applying a regex pattern, especially when processing a large list of emails.

By performing in-place reconstruction of the string during the period removal loop, the code eliminates the need for auxiliary data structures such as character arrays or lists. Each iteration creates a new string that is one character shorter, but the overall number of allocations is bounded by the number of periods in the local part, which is typically small.

The use of a generator expression inside the set constructor (set(filter(m) for m in emails)) ensures that each email is filtered lazily, avoiding the creation of an intermediate list of transformed emails. This reduces peak memory consumption, as only one transformed string exists at a time before being inserted into the set.

The final len call operates on the set, which provides O(1) average-time access to its size attribute, delivering the result without additional traversal.

Overall, the combination of targeted string operations, lazy evaluation, and direct set construction yields an efficient solution for counting unique normalized email addresses.

written by openai gpt

ELI5 - Simplified Explanation

Imagine you have a big pile of letters to deliver, but some of the addresses are written in a messy way. This code is like a little robot helper that tidies up the addresses so you know which ones are actually headed to the same person.

First, the robot looks at the name part of the email address before the at symbol. If it sees any tiny dots hiding in there, it picks them up and throws them away, because those dots do not change who is getting the mail.

Next, the robot looks for a plus sign. If it finds one, it knows that everything after the plus sign and before the at symbol is just extra notes, so it takes those notes and tosses them out. It keeps the part after the at symbol exactly as it is because that tells us which house the mail is going to.

Once the robot finishes cleaning up an address, it puts the result into a special box. The rule for this box is that it never keeps two of the same thing. If the robot cleans up two different looking addresses and they both turn out to be identical, the box only holds one of them. In the end, counting the items inside this box tells you how many different people are waiting for mail.

written by google gemini

Code Quality

Several LLMs ranked my code quality on a scale from 1 (very poor) to 10 (outstanding):
n = 26 | median = 6 | μ = 5.96 | σ = 1.34

Complexity Analysis

Time: polynomial
As a rule of thumb, the maximum input size for one second execution time is
  • factorial: up to 10 elements
  • exponential: up to 20 elements
  • polynomial: a few thousand elements
  • loglinear: up to 100,000 elements
  • linear: a few million elements
  • logarithmic: a billion elements
  • constant: practically unlimited
generated automatically

Other Solutions

I wrote a basic crawler to find other solutions to that problem.
Obviously, I did that after I solved the problem myself.

In general, AlgoMonster and WalkCCC solutions are high-quality and always worth a look.
I haven't checked most of the other solutions, but they might be helpful as well.

And of course, the LeetCode forum is always a good place (or even the best) to look for alternative approaches.

(click on a column header to sort the table)
author
author solutions
www AlgoMonster C++ Java Python TS
WalkCCC C++ Java Python
leetcode.ca C++ Java Python JS Go Rust TS
LeetcodeHelp C++ Java Python
Progiez C++ Java Python
github BigEggStudy C#
black-shadows C++
charles-wangkai Java
DimitrisJim C
everthis JS
fartem Ruby
fengvyi C++
fsq C++
haoel C++
JoshCrozier JS
keineahnung2345 C++
Krazune C++
lkwq007 Python
lzl124631x C++
qiyuangong Java
suxrobGM C#
varunu28 Java
YuriSpiridonov Python
AlgoMonster C++, Python, Java, TypeScript
WalkCCC C++, Python, Java
leetcode.ca C++, Go, Java, JavaScript, Python, Rust, TypeScript
LeetcodeHelp C++, Java, Python
Progiez C++, Java, Python
BigEggStudy C#
black-shadows C++, C++
charles-wangkai Java
DimitrisJim C
everthis JavaScript
fartem Ruby
fengvyi C++
fsq C++
haoel C++
JoshCrozier JavaScript
keineahnung2345 C++
Krazune C++
lkwq007 Python
lzl124631x C++
qiyuangong Java
suxrobGM C#
varunu28 Java
YuriSpiridonov Python
38 links | 23 authors | 10 languages

Videos

4 videos   did my crawler come up with invalid videos ? drop me a line.

Solved On The Same Day

1 easy
2 solutions   October 28, 2023

Extra

There are a few extra pages giving you some insights into my setup:
  • Tech Stack - my development environment and tools
  • AI - how AI helped me create this website (NOT solving the problems)
  • Progress - progression over time
  • Quality - code quality