Rewrite char frequency of string as comprehension

Viewed 657

The following procedural code snippet computes the character frequency of a text string and writes inside a dictionary. The dictionary has the characters as keys and the frequency as values.

text = "asampletextstring"
char_count = {}
for char in text:
    if char_count.get(char):
        char_count[char] += 1
    else:
        char_count[char] = 1

My question is, is it possible to rewrite the above snippet as a comprehension?

4 Answers

It is possible, but is inefficient:

text = "asampletextstring"

char_count = { char : text.count(char) for char in text }

print(char_count)

Output

{'s': 2, 'x': 1, 'p': 1, 'm': 1, 'e': 2, 'r': 1, 'n': 1, 'g': 1, 'a': 2, 'i': 1, 'l': 1, 't': 3}

You could write a shorter version of your code:

char_count = {}
for char in text:
    char_count[char] = char_count.get(char, 0) + 1

Could use set() here to avoid encountering the character 2 or more times.

text = "asampletextstring"
dict1 = {ch: text.count(ch) for ch in set(text)}

print(dict1)
{'s': 2, 'r': 1, 'i': 1, 'n': 1, 'a': 2, 'e': 2, 'p': 1, 't': 3, 'x': 1, 'l': 1, 'g': 1, 'm': 1}

Was curious to look into the performance of various approaches and to prove comprehensions are not good each time I did some analysis using dictionary comprehensions, dictionary comprehension by converting input to sets and traditional for loops. It makes sense why comprehension is expensive here as .count() is iterating over the entire text each time to count the frequency of single char

from timeit import timeit

print('Approach 1 without set compehrension: {}'.format(timeit ('{ch: text.count(ch) for ch in text}',setup='text = "asampletextstring"',number=1000000)))
print('Approach 2 with set compehrension: {}'.format(timeit ('{ch: text.count(ch) for ch in set(text)}',setup='text = "asampletextstring"',number=1000000)))
print('Approach 3 simple loops :{}'.format(timeit('for c in text:char_count[c] = char_count.get(c, 0) + 1',setup='text = "asampletextstring";char_count={};',number=1000000)))
print('Approach 4 Counter :{}'.format(timeit('Counter(text)',setup='text = "asampletextstring";from collections import Counter;',number=1000000)))

Output:

Approach 1 without set compehrension: 4.43441867505
Approach 2 with set compehrension: 3.98101747791
Approach 3 simple loops :2.60219633984
Approach 4 Counter :7.54261124884

Rewrite - not really, I do not see any easy way. The best I arrived requires an additional dictionary.

d = {}
{ c: d.get(c, 0)  for c in text if d.update( {c: d.get(c,0) + 1} ) or True}

One will be able to get one-liner in Python 3.8, but by (ab)using assignment expressions

Related