Как эффективно рассчитать префиксную сумму частот символов в строке?

Скажи, у меня есть строка

s = 'AAABBBCAB'

Как я могу эффективно рассчитать сумму префиксов частот каждого символа в строке, то есть:

psum = [{'A': 1}, {'A': 2}, {'A': 3}, {'A': 3, 'B': 1}, {'A': 3, 'B': 2}, {'A': 3, 'B': 3}, {'A': 3, 'B': 3, 'C': 1}, {'A': 4, 'B': 3, 'C': 1}, {'A': 4, 'B': 4, 'C': 1}]

Ответ 1

Вы можете сделать это в одну строку, используя itertools.accumulate и collections.Counter:

from collections import Counter
from itertools import accumulate

s = 'AAABBBCAB'
psum = list(accumulate(map(Counter, s)))

Это дает вам список объектов Counter. Теперь, чтобы получить частоты для любой подстроки s за O (1) времени, вы можете просто вычесть счетчики, например:

>>> psum[6] - psum[1]  # get frequencies for s[2:7]
Counter({'B': 3, 'A': 1, 'C': 1})

Ответ 2

это вариант:

from collections import Counter

c = Counter()
s = 'AAABBBCAB'

psum = []
for char in s:
    c.update(char)
    psum.append(dict(c))

# [{'A': 1}, {'A': 2}, {'A': 3}, {'A': 3, 'B': 1}, {'A': 3, 'B': 2}, 
#  {'A': 3, 'B': 3}, {'A': 3, 'B': 3, 'C': 1}, {'A': 4, 'B': 3, 'C': 1},
#  {'A': 4, 'B': 4, 'C': 1}]

Я использую collections.Counter, чтобы сохранить "промежуточную сумму" и добавить (копию результата) в список psum. таким образом, я повторяю только один раз над строкой s.

если вы предпочитаете иметь collections.Counter объекты в своем результате, вы можете изменить последнюю строку на

psum.append(c.copy())

чтобы получить

[Counter({'A': 1}), Counter({'A': 2}), ...
 Counter({'A': 4, 'B': 4, 'C': 1})]

тот же результат также может быть достигнут с этим (использование accumulate было впервые предложено в ответе Евгения Ярмаша; я просто избегаю map в пользу выражения генератора) :

from itertools import accumulate
from collections import Counter

s = "AAABBBCAB"
psum = list(accumulate(Counter(char) for char in s))

просто для полноты (поскольку здесь пока нет ответа "чистый dict"). если вы не хотите использовать Counter или defaultdict, вы также можете использовать это:

c = {}
s = 'AAABBBCAB'

psum = []
for char in s:
    c[char] = c.get(char, 0) + 1
    psum.append(c.copy())

хотя defaultdict обычно более производительный, чем dict.get(key, default).

Ответ 3

Проще всего было бы использовать объект Counter из коллекций.

from collections import Counter

s = 'AAABBBCAB'

[ dict(Counter(s[:i]) for i in range(1,len(s))]

Урожайность:

[{'A': 1},  {'A': 2},  {'A': 3},  {'A': 3, 'B': 1},  {'A': 3, 'B': 2},
{'A': 3, 'B': 3},  {'A': 3, 'B': 3, 'C': 1},  {'A': 4, 'B': 3, 'C': 1}]

Ответ 4

Вам даже не нужен счетчик для этого, достаточно дефолта!

from collections import defaultdict

c = defaultdict(int)
s = 'AAABBBCAB'

psum = []

#iterate through the character
for char in s:
    #Update count for each character
    c[char] +=1
    #Add the updated dictionary to the output list
    psum.append(dict(c))

print(psum)

Выход выглядит как

[{'A': 1}, {'A': 2}, {'A': 3}, {'A': 3, 'B': 1}, 
{'A': 3, 'B': 2}, {'A': 3, 'B': 3}, 
{'A': 3, 'B': 3, 'C': 1}, {'A': 4, 'B': 3, 'C': 1}, 
{'A': 4, 'B': 4, 'C': 1}]

Ответ 5

В Python 3.8 вы можете использовать понимание списка с помощью выражения присваивания (он же "оператор моржа"):

>>> from collections import Counter
>>> s = 'AAABBBCAB'
>>> c = Counter()
>>> [c := c + Counter(x) for x in s]
[Counter({'A': 1}), Counter({'A': 2}), Counter({'A': 3}), Counter({'A': 3, 'B': 1}), Counter({'A': 3, 'B': 2}), Counter({'A': 3, 'B': 3}), Counter({'A': 3, 'B': 3, 'C': 1}), Counter({'A': 4, 'B': 3, 'C': 1}), Counter({'A': 4, 'B': 4, 'C': 1})]

Ответ 6

Попробуйте это:

>>> s = 'AAABBBCAB'
>>> res = [{letter_: s[0:i+1].count(letter_) for letter_ in set(s[0:i+1])} for i in range(len(s))]
>>> res
[{'A': 1}, {'A': 2}, {'A': 3}, {'B': 1, 'A': 3}, {'B': 2, 'A': 3}, {'B': 3, 'A': 3}, {'B': 3, 'C': 1, 'A': 3}, {'B': 3, 'C': 1, 'A': 4}, {'B': 4, 'C': 1, 'A': 4}]