Contest Solution: Super Freq

The Problem

A character is known to its homeboys as a super freq if it occurs with frequency greater than 15% in a given passage of text. Write a program that reads an ASCII text file and identifies all the English alphabet (A-Z, a-z) super freqs in that text file. Your program should be case insensitive.

Sample Input

Your program should take its input from the text file prob2.in. Three examples (A, B and C) of the possible content of this file are shown below:

(A) Sally sells sea shells by the sea shore.
(B) How now brown cow.
(C) Hey Sam!

Sample Output

Your program should direct its output to the screen. Appropriate output for the sample inputs (A, B and C) is shown below:

(A) S is a super freq.
(B) O is a super freq.
W is a super freq.
(C) There are no super freqs.

superfreq.py

import re
from collections import Counter

with open('prob2.in') as f:
for line in f:
s = re.sub(r"[^a-z]+", '', line.lower())
d, c = Counter(s), 0
for e in d:
if d[e] / len(line) > 0.15:
print('{0} is a super freq.'.format(e.upper()))
c += 1
if (c == 0):
print('There are no super freqs.')

Any advice on performance enhancement and solution simplification or that is topical is appreciated!

The variable names c, d, and e are too cryptic. (f is fine, because it's a customary variable for a file. In my solution below, I use c consistently for characters during iteration, which is also acceptable.)

Why do you normalize the case using line.lower(), when the sample output suggests that uppercase output is preferred?

In my opinion, the regular expression is overkill here.

The challenge is ambiguous as to what exactly constitutes "15% in a given passage of text". Does the denominator include the spaces and punctuation? That's debatable. Should the denominator include the trailing newline? Probably not, but you should be aware that you did.

Personally, I'd prefer to collect all of the super freqs using a list comprehension, instead of checking for the if (c == 0): … case. In any case, there shouldn't be parentheses there in standard Python style.

from collections import Counter

with open('prob2.in') as f:
for line in f:
counts = Counter(c for c in line.upper() if 'A' <= c <= 'Z')
super_freqs = [c for c, n in counts.items() if n / len(line) > 0.15]
if not super_freqs:
print('There are no super freqs.')
else:
for c in super_freqs:
print('{0} is a super freq.'.format(c))
• The regex should get rid of the newline; does it not? I can just throw a .trim() on my line? – T145 Nov 2 '18 at 11:13
• But you divide by len(line), not len(s). – 200_success Nov 2 '18 at 13:51
with open('prob2.in') as f:
for line in f:

That looks buggy to me. The spec says (my emphasis)

that reads an ASCII text file and identifies all the English alphabet (A-Z, a-z) super freqs in that text file

and

Three examples (A, B and C) of the possible content of this file are shown below:

It doesn't say anything about the text file being 1 line long, or having different test cases on different lines.

s = re.sub(r"[^a-z]+", '', line.lower())

Why line.lower()? It seems to make more sense to convert it to upper case since that's what you use later for output.

d, c = Counter(s), 0
for e in d:

These are not descriptive names.

if d[e] / len(line) > 0.15:

Why use floating point numbers and introduce potential bugs with rounding? Some elementary arithmetic manipulation would give a calculation which is entirely in integers.

if (c == 0):

The brackets are unconventional in Python.

• I set the line to lower so that the regex only needed to catch lowercase letters. Idk if simply adding the capital cases to the regex is faster than setting the string to lower and checking it. – T145 Nov 2 '18 at 13:31
• You've missed my point. It seems to me that line.upper() is strictly preferable to line.lower(). – Peter Taylor Nov 2 '18 at 19:48