Not the exercise, just the tool. Counter reads a sequence once and gives you a dict of how many times each item appeared.
from collections import Counter
votes = ["tea", "coffee", "tea", "water", "tea"]
tally = Counter(votes)
print(tally) # -> Counter({'tea': 3, 'coffee': 1, 'water': 1})
print(tally["coffee"]) # -> 1
print(tally["juice"]) # -> 0 missing items count as zero, no KeyError
print(Counter("hello")) # -> Counter({'l': 2, 'h': 1, 'e': 1, 'o': 1})
- One pass to count everything
Counter looks at each item once. After that, "how many of these are there?" is a dict lookup, not another walk through the input.
- The counts do not remember order
The printout lists the most common first. For "which one came first" you still need the original sequence, which is why this problem takes two passes.
- A string is a sequence of characters
Counter("hello") counts letters with no split and no loop of your own.
Two questions: how often does each character appear, and which comes first? Answer them in that order, one pass each.
Counter(text) answers the first. Then enumerate(text) walks the string in order with each position.
counts = Counter(text); for position, char in enumerate(text): if counts[char] == 1, return position. After the loop, return -1.
from collections import Counter def first_unique(text): counts = ____(text) for position, char in ____(text): if counts[char] == 1: return ____ return -1
from collections import Counter
def first_unique(text):
counts = Counter(text)
for position, char in enumerate(text):
if counts[char] == 1:
return position
return -1
Counting is one walk through the string and finding is another. They are not nested, so doubling the string doubles the time, where counting each character separately would make it four times slower.
The Counter knows how many; only the string knows the order. Looping over counts.items() instead happens to work in modern Python, but it leans on an insertion order the question never promised.
Naming text.count() inside a loop as quadratic even though it is one line, and saying that the extra memory is at most one entry per distinct character.
When the input is a stream you cannot read twice, like a live log, keep the counts and the order of first appearance as you go: a dict of counts plus a record of when each item first appeared, and the answer is the earliest one whose count is still 1.
- Return the character itself, and decide what the "not found" value should be when the answer is text rather than a position.
- Ignore case, so "A" and "a" count as one letter, and still return the position of the original.
- Do it in one pass for a stream you cannot read twice.