簡體   English   中英

在 Python 中計算字符串中的重復字符

[英]Counting repeated characters in a string in Python

我想計算每個字符在字符串中重復的次數。 除了比較來自 AZ 的字符串的每個字符並增加計數器之外,還有什么特別的方法可以做到嗎?

更新(參考Anthony 的回答):到目前為止,無論您提出什么建議,我都必須寫 26 次。 有更容易的方法嗎?

import collections

d = collections.defaultdict(int)
for c in thestring:
    d[c] += 1

collections.defaultdict就像一個dict (實際上是它的子類),但是當一個條目被尋找但沒有找到時,它不會報告它沒有它,而是通過調用提供的 0 參數可調用來創建並插入它。 最流行的是defaultdict(int) ,用於計數(或等效地,制作多集 AKA 包數據結構)和defaultdict(list) ,它永遠不需要使用.setdefault(akey, []).append(avalue)和類似的尷尬習語。

所以一旦你完成了這個d是一個類似 dict 的容器,將每個字符映射到它出現的次數,當然,你可以以任何你喜歡的方式發出它。 例如,最流行的字符優先:

for c in sorted(d, key=d.get, reverse=True):
  print '%s %6d' % (c, d[c])

我的第一個想法是這樣做:

chars = "abcdefghijklmnopqrstuvwxyz"
check_string = "i am checking this string to see how many times each character appears"

for char in chars:
  count = check_string.count(char)
  if count > 1:
    print char, count

然而,這不是一個好主意! 這將掃描字符串 26 次,因此您可能會比其他一些答案多做 26 倍的工作。 你真的應該這樣做:

count = {}
for s in check_string:
  if s in count:
    count[s] += 1
  else:
    count[s] = 1

for key in count:
  if count[key] > 1:
    print key, count[key]

這確保您只遍歷字符串一次,而不是 26 次。

另外,Alex 的回答很好 - 我不熟悉集合模塊。 我將來會使用它。 他的回答比我的更簡潔,技術上也更勝一籌。 我建議使用他的代碼而不是我的代碼。

Python 2.7+ 包括collections.Counter類:

import collections
results = collections.Counter(the_string)
print(results)

盛大的性能比較

滾動到TL;DR 圖表的末尾

由於我“無事可做”(理解:我只是有很多工作),我決定做一個小小的表演比賽。 我收集了最明智或最有趣的答案,並在CPython 3.5.1 中對它們進行了一些簡單的timeit 我只用一個字符串測試了它們,在我的例子中這是一個典型的輸入:

>>> s = 'ZDXMZKMXFDKXZFKZ'
>>> len(s)
16

請注意,不同輸入的結果可能會有所不同,無論是字符串的長度不同或不同字符的數量不同,還是每個字符的平均出現次數不同。


不要重新發明輪子

Python 使我們變得簡單。 collections.Counter類完全符合我們的要求,而且還有更多功能。 到目前為止,它的用法是這里提到的所有方法中最簡單的。

取自@oefe ,不錯的發現

>>> timeit('Counter(s)', globals=locals())
8.208566107001388

Counter走得更遠,這就是為什么它需要這么長時間。

¿字典,理解?

讓我們嘗試使用一個簡單的dict來代替。 首先,讓我們使用 dict comprehension 以聲明方式進行操作。

我自己想出了這個......

>>> timeit('{c: s.count(c) for c in s}', globals=locals())
4.551155784000002

這將從頭到尾遍歷s ,並且對於每個字符,它將計算其在s出現的次數。 由於s包含重復字符,上述方法多次搜索s以查找相同的字符。 結果自然總是一樣的。 因此,讓我們計算每個字符出現的次數。

我自己想出了這個, @IrshadBhat也是如此

>>> timeit('{c: s.count(c) for c in set(s)}', globals=locals())
3.1484066140001232

更好的。 但是我們仍然需要搜索字符串來計算出現次數。 每個不同的字符進行一次搜索。 這意味着我們將不止一次讀取字符串。 我們可以做得更好! 但為此,我們必須擺脫我們的聲明主義高馬,進入一種勢在必行的心態。

非凡的代碼

AKA 必須抓住他們!

靈感來自@anthony

>>> timeit('''
... d = {}
... for c in s:
...   try:
...     d[c] += 1
...   except KeyError:
...     d[c] = 1
... ''', globals=locals())
3.7060273620008957

嗯,值得一試。 如果您深入研究 Python 源代碼(我不能肯定地說,因為我從未真正這樣做過),您可能會發現,當您執行except ExceptionType ,Python 必須檢查引發的ExceptionType實際上是ExceptionType還是其他一些ExceptionType類型。 順便說一句,讓我們看看如果我們省略該檢查並捕獲所有異常需要多長時間。

@anthony 制作

>>> timeit('''
... d = {}
... for c in s:
...   try:
...     d[c] += 1
...   except:
...     d[c] = 1
... ''', globals=locals())
3.3506563019982423

它確實節省了一些時間,因此人們可能會想將其用作某種優化。
不要那樣做! 或者實際上這樣做。 現在做:

插曲 1

import time
while True:
  try:
    time.sleep(1)
  except:
    print("You're trapped in your own trap!")

你看? 除了其他事情之外,它還捕獲KeyboardInterrupt 事實上,它捕獲了所有的異常。 包括你可能甚至沒有聽說過的,比如SystemExit

插曲2

import sys
try:
  print("Goodbye. I'm going to die soon.")
  sys.exit()
except:
  print('BACK FROM THE DEAD!!!')

現在回到計數字母和數字以及其他字符。

玩追趕

例外不是要走的路。 你必須努力追上他們,當你終於趕上時,他們只是吐了你,然后揚起眉毛,好像這是你的錯。 幸運的是,勇敢的人為我們鋪平了道路,因此我們可以消除例外情況,至少在這個小練習中。

dict類有一個很好的方法- get -這使我們能夠從字典檢索項目,就像d[k] 除非鍵k不在字典中,否則它可以返回一個默認值。 讓我們使用該方法而不是擺弄異常。

歸功於@Usman

>>> timeit('''
... d = {}
... for c in s:
...   d[c] = d.get(c, 0) + 1
... ''', globals=locals())
3.2133633289995487

幾乎和基於集合的字典理解一樣快。 在更大的輸入上,這個可能會更快。

為工作使用正確的工具

對於至少知識淵博的 Python 程序員來說,首先想到的可能是defaultdict 它和上面的版本做的事情幾乎一樣,除了你給它一個值工廠而不是一個值。 這可能會導致一些開銷,因為必須為每個缺失的鍵單獨“構造”該值。 讓我們看看它的表現如何。

希望@AlexMartelli不會因為from collections import defaultdict而把我釘在十字架上

>>> timeit('''
... dd = defaultdict(int)
... for c in s:
...   dd[c] += 1
... ''', globals=locals())
3.3430528169992613

沒有那么糟糕。 我想說的是,執行時間的增加是為提高可讀性而付出的小代價。 但是,我們也看重性能,我們不會就此止步。 讓我們更進一步,用零預填充字典。 這樣我們就不必每次都檢查該項目是否已經存在。

@sqram 致敬

>>> timeit('''
... d = dict.fromkeys(s, 0)
... for c in s:
...   d[c] += 1
... ''', globals=locals())
2.6081761489986093

那挺好的。 Counter快三倍多,但仍然足夠簡單。 就個人而言,這是我最喜歡的,以防您以后不想添加新角色。 即使你這樣做了,你仍然可以做到。 它不如其他版本方便:

d.update({ c: 0 for c in set(other_string) - d.keys() })


實用性勝過純度(除非它不是很實用)

現在有點不同的計數器。 @IdanK想出了一些有趣的東西。 代替使用哈希表(又名字典又名dict ),我們可以避免哈希沖突的風險和隨之而來的解析開銷。 我們還可以避免散列鍵的開銷,以及額外的未占用表空間。 我們可以使用list 字符的 ASCII 值將是索引,它們的計數將是值。 正如@IdanK 所指出的那樣,這個列表讓我們可以恆定時間訪問一個字符的數量。 我們所要做的就是使用內置函數ord將每個字符從str轉換為int 這將為我們提供列表中的索引,然后我們將使用它來增加字符的計數。 所以我們要做的是:我們用零初始化列表,完成工作,然后將列表轉換為dict 這個dict將只包含那些具有非零計數的字符,以使其與其他版本兼容。

作為旁注,此技術用於稱為計數排序計數排序的線性時間排序算法。 它非常有效,但排序的值的范圍是有限的,因為每個值都必須有自己的計數器。 要對 32 位整數序列進行排序,將需要 43 億個計數器。

>>> timeit('''
... counts = [0 for _ in range(256)]
... for c in s:
...   counts[ord(c)] += 1
... d = {chr(i): count for i,count in enumerate(counts) if count != 0}
... ''', globals=locals())
25.438595562001865

哎喲! 不酷! 讓我們試着看看當我們省略構建字典時需要多長時間。

>>> timeit('''
... counts = [0 for _ in range(256)]
... for c in s:
...   counts[ord(c)] += 1
... ''', globals=locals())
10.564866792999965

還是不好。 但是等等,什么是[0 for _ in range(256)] 我們不能寫得更簡單嗎? [0] * 256怎么樣? 那更干凈。 但它會表現得更好嗎?

>>> timeit('''
... counts = [0] * 256
... for c in s:
...   counts[ord(c)] += 1
... ''', globals=locals())
3.290163638001104

相當。 現在讓我們把字典放回去。

>>> timeit('''
... counts = [0] * 256
... for c in s:
...   counts[ord(c)] += 1
... d = {chr(i): count for i,count in enumerate(counts) if count != 0}
... ''', globals=locals())
18.000623562998953

幾乎慢了六倍。 為什么需要這么長時間? 因為當我們enumerate(counts) ,我們必須檢查 256 個計數中的每一個,看看它是否為零。 但是我們已經知道哪些計數為零,哪些不是。

>>> timeit('''
... counts = [0] * 256
... for c in s:
...   counts[ord(c)] += 1
... d = {c: counts[ord(c)] for c in set(s)}
... ''', globals=locals())
5.826531438000529

它可能不會比那更好,至少對於這么小的輸入不會。 此外,它僅可用於 8 位 EASCII 字符。 Облять!

最終獲勝者是...

>>> timeit('''
... d = {}
... for c in s:
...   if c in d:
...     d[c] += 1
...   else:
...     d[c] = 1
... ''', globals=locals())
1.8509794599995075

是的。 即使您每次都必須檢查c是否在d ,對於此輸入,它也是最快的方法。 沒有預先填充d會使它更快(同樣,對於這個 input )。 它比Counterdefaultdict詳細得多,但也更有效。


這就是所有人

這個小練習給我們上了一課:在優化時,始終衡量性能,最好是根據您的預期輸入。 針對常見情況進行優化。 不要僅僅因為它的漸近復雜度較低就假設某事實際上更有效。 最后但並非最不重要的一點是,請記住可讀性。 嘗試在“計算機友好”和“人性化”之間找到折衷。



更新

@MartijnPieters告訴我 Python 3 中可用的函數collections._count_elements

Help on built-in function _count_elements in module _collections:

_count_elements(...)
    _count_elements(mapping, iterable) -> None

    Count elements in the iterable, updating the mappping

這個函數是用 C 實現的,所以它應該更快,但這種額外的性能是有代價的。 價格與 Python 2 甚至未來版本不兼容,因為我們使用的是私有函數。

文檔

[...] 帶有下划線前綴的名稱(例如_spam )應被視為 API 的非公開部分(無論是函數、方法還是數據成員)。 它應被視為實施細節,如有更改,恕不另行通知。

也就是說,如果您仍然想在每次迭代中節省 620 納秒:

>>> timeit('''
... d = {}
... _count_elements(d, s)
... ''', globals=locals())
1.229239897998923



更新 2:大字符串

我認為在一些更大的輸入上重新運​​行測試可能是一個好主意,因為 16 個字符的字符串是一個很小的輸入,所有可能的解決方案都相當快(在 30 毫秒內迭代 1,000 次)

我決定使用莎士比亞全集作為測試語料庫,結果證明這是一個很大的挑戰(因為它的大小超過 5MiB 😅)。 我只使用了它的前 100,000 個字符,我不得不將迭代次數限制在 1,000,000 到 1,000 之間。

import urllib.request
url = 'https://ocw.mit.edu/ans7870/6/6.006/s08/lecturenotes/files/t8.shakespeare.txt'
s = urllib.request.urlopen(url).read(100_000)

collections.Counter在一個小的輸入上真的很慢,但桌子已經轉了

Counter(s)

=> 7.63926783799991

Naïve Θ(n 2 )時間字典理解根本行不通

{c: s.count(c) for c in s}

=> 15347.603935000052s (tested on 10 iterations; adjusted for 1000)

智能Θ(n)時間字典理解工作正常

{c: s.count(c) for c in set(s)}

=> 8.882608592999986

異常笨拙而緩慢

d = {}
for c in s:
  try:
    d[c] += 1
  except KeyError:
    d[c] = 1

=> 21.26615508399982

省略異常類型檢查不會節省時間(因為異常只拋出幾次)

d = {}
for c in s:
  try:
    d[c] += 1
  except:
    d[c] = 1

=> 21.943328911999743

dict.get看起來不錯,但運行緩慢

d = {}
for c in s:
  d[c] = d.get(c, 0) + 1

=> 28.530086210000007

collections.defaultdict也不是很快

dd = defaultdict(int)
for c in s:
  dd[c] += 1

=> 19.43012963199999

dict.fromkeys需要讀取(很長的)字符串兩次

d = dict.fromkeys(s, 0)
for c in s:
  d[c] += 1

=> 22.70960557699999

使用list而不是dict既不好也不快

counts = [0 for _ in range(256)]
for c in s:
  counts[ord(c)] += 1

d = {chr(i): count for i,count in enumerate(counts) if count != 0}

=> 26.535474792000002

省略對dict的最終轉換無濟於事

counts = [0 for _ in range(256)]
for c in s:
  counts[ord(c)] += 1

=> 26.27811567400005

您如何構造list並不重要,因為它不是瓶頸

counts = [0] * 256
for c in s:
  counts[ord(c)] += 1

=> 25.863524940000048


counts = [0] * 256
for c in s:
  counts[ord(c)] += 1

d = {chr(i): count for i,count in enumerate(counts) if count != 0}

=> 26.416733378000004

如果轉換listdict “聰明”的方式,它更慢(因為你遍歷字符串兩次)

counts = [0] * 256
for c in s:
  counts[ord(c)] += 1

d = {c: counts[ord(c)] for c in set(s)}

=> 29.492915620000076

dict.__contains__變體對於小字符串可能很快,但對於大字符串則不然

d = {}
for c in s:
  if c in d:
    d[c] += 1
  else:
    d[c] = 1

=> 23.773295123000025

collections._count_elementscollections.Counter _count_elements內部使用_count_elements

d = {}
_count_elements(d, s)

=> 7.5814381919999505


最終判決:除非您不能或不想,否則請使用collections.Counter :)



附錄: NumPy

numpy包提供了一個方法numpy.unique ,它(幾乎)精確地完成了我們想要的。

這種方法的工作方式與上述所有方法都非常不同:

  • 它首先使用快速排序對輸入的副本進行排序,這在最壞情況下是O(n 2 )時間操作,盡管平均為O(n log n) ,在最佳情況下為O(n)

  • 然后它在索引處創建一個包含True的“掩碼”數組,其中開始運行相同的值,即。 在值與先前值不同的索引處。 重復值在掩碼中產生False 示例: [5,5,5,8,9,9]生成掩碼[True, False, False, True, True, False]

  • 然后,此掩碼用於從以下代碼中的已排序輸入 ‒ unique_chars中提取唯一值。 在我們的示例中,它們將是[5, 8, 9]

  • 掩碼中True值的位置被放入一個數組中,輸入的長度附加在該數組的末尾。 對於上面的示例,此數組將是[0, 3, 4, 6]

  • 對於這個數組,計算其元素之間的差異,例如。 [3, 1, 2] 這些是以下代碼中已排序數組 ‒ char_counts中元素的相應計數。

  • 最后,我們通過壓縮unique_charschar_counts創建一個字典: {5: 3, 8: 1, 9: 2}


import numpy as np

def count_chars(s):
  # The following statement needs to be changed for different input types.
  # Our input `s` is actually of type `bytes`, so we use `np.frombuffer`.
  # For inputs of type `str`, change `np.frombuffer` to `np.fromstring`
  #  or transform the input into a `bytes` instance.
  arr = np.frombuffer(s, dtype=np.uint8)

  unique_chars, char_counts = np.unique(arr, return_counts=True)

  return dict(zip(unique_chars, char_counts))

對於測試輸入( 莎士比亞全集的前 100,000 個字符),此方法的性能優於此處測試的任何其他方法。 但請注意,在不同的輸入下,這種方法可能會產生比其他方法更差的性能。 輸入的預排序和每個元素的重復次數是影響性能的重要因素。

count_chars(s)

=> 2.960809530000006


如果您正在考慮使用此方法,因為它的速度是collections.Counter兩倍多,請考慮:

  • collections.Counter具有線性時間復雜度。 numpy.unique最好是線性,最壞是二次的。

  • 加速並不是那么顯着——在長度為 100,000 的輸入上每次迭代可以節省大約 3.5 毫秒。

  • 使用numpy.unique顯然需要numpy

考慮到這一點,除非您需要非常快,否則使用Counter似乎是合理的。 在這種情況下,您最好知道自己在做什么,否則使用numpy最終會比沒有它慢。



附錄 2:有點有用的圖

我在莎士比亞全集的前綴上運行了上述13種不同的方法,並制作了一個交互式情節。 請注意,在圖中,前綴和持續時間均以對數刻度顯示(使用的前綴長度呈指數增長)。 單擊圖例中的項目以在圖中顯示/隱藏它們。

互動圖(點擊!)

點擊打開!

這是我在不導入額外模塊的情況下能想到的最短、最實用的方法。

text = "hello cruel world. This is a sample text"
d = dict.fromkeys(text, 0)
for c in text: d[c] += 1

打印 d['a'] 將輸出 2

而且它也很快。

如果有人正在尋找沒有collections模塊的最簡單方法。 我想這會有所幫助:

>>> s = "asldaksldkalskdla"
>>> {i:s.count(i) for i in set(s)}
{'a': 4, 'd': 3, 'k': 3, 's': 3, 'l': 4}

要么

>>> [(i,s.count(i)) for i in set(s)]
[('a', 4), ('k', 3), ('s', 3), ('l', 4), ('d', 3)]

您想使用dict

#!/usr/bin/env python

input = "this is a string"

d = {}

for c in input:
    try:
        d[c] += 1
    except:
        d[c] = 1

for k in d.keys():
    print "%s: %d" % (k, d[k])
dict = {}
for i in set(str):
    b = str.count(i, 0, len(str))
    dict[i] = b
print dict

如果我的字符串是:

str = "this is string!"

上面的代碼將打印:

{'!': 1, ' ': 2, 'g': 1, 'i': 3, 'h': 1, 'n': 1, 's': 3, 'r': 1, 't': 2}

如果只是計算給定字符串中給定字符的重復次數的問題,請嘗試這樣的操作。

 word = "babulibobablingo" letter = 'b' if letter in word: print(word.count(letter))
inputString =  input("Enter a String:")
countedArray = {}

for char in inputString:
    if char in countedArray:  
        countedArray[char] += 1    
    else:
        countedArray[char] = 1
    
print(countedArray) 

您可以使用字典:

s = "asldaksldkalskdla"
dict = {}
for letter in s:
 if letter not in dict.keys():
  dict[letter] = 1
 else:
  dict[letter] += 1

print dict

我可以用兩只手數出我知道 Python 的天數,所以如果我回答一些愚蠢的問題,請原諒我 :)

我想為什么不使用列表而不是使用字典? 我不確定列表和字典是如何在 Python 中實現的,因此必須對其進行測量才能知道什么更快。

如果這是 C++,我將只使用普通的 c-array/vector 進行恆定時間訪問(這肯定會更快),但我不知道 Python 中相應的數據類型是什么(如果有的話......):

count = [0 for i in range(26)]

for c in ''.join(s.lower().split()): # get rid of whitespaces and capital letters
    count[ord(c) - 97] += 1          # ord('a') == 97

也可以使列表的大小為 ord('z') 然后在任何地方都去掉 97 減法,但是如果優化,為什么不一路:)

編輯:評論者建議加入/拆分不值得使用列表的可能收益,所以我想為什么不擺脫它:

count = [0 for i in range(26)]

for c in s:
    if c.isalpha(): count[ord(c.lower()) - 97] += 1

這將顯示一個帶有出現次數的字符字典

str = 'aabcdefghijklmnopqrstuvwxyz'
mydict = {}
for char in str:
    mydict[char]=mydict.get(char,0)+1
 print mydict

要計算字符串中的字符,您必須使用YOUR_VARİABLE.count('WHAT_YOU_WANT_TO_COUNT')

如果需要匯總,則必須使用 count() 函數。

variable = 'turkiye'
print(variable.count('u'))

輸出:1

這是解決方案..

my_list=[]
history=""
history_count=0
my_str="happppyyyy"


for letter in my_str:
    if letter in history:
        my_list.remove((history,history_count))
        history=letter
        history_count+=1

    else:
        history_count=0
        history_count+=1
        history=letter


my_list.append((history,history_count))    


print my_list
s = 'today is sunday i would like to relax'
numberOfDuplicatedChar = len(s) - len(set(s))

# set重復的元素。

下面的代碼對我有用,而無需尋找任何其他 Python 庫。

def count_repeated_letter(string1):
    list1=[]

    for letter in string1:
        if string1.count(letter)>=2:
            if letter not in list1:
                list1.append(letter)


    for item in list1:
        if item!= " ":
            print(item,string1.count(item))


count_repeated_letter('letter has 1 e and 2 e and 1 t and two t')

輸出:

e 4
t 5
a 4
1 2
n 3
d 3

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM