[英]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 )。 它比Counter
或defaultdict
详细得多,但也更有效。
这个小练习给我们上了一课:在优化时,始终衡量性能,最好是根据您的预期输入。 针对常见情况进行优化。 不要仅仅因为它的渐近复杂度较低就假设某事实际上更有效。 最后但并非最不重要的一点是,请记住可读性。 尝试在“计算机友好”和“人性化”之间找到折衷。
@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
我认为在一些更大的输入上重新运行测试可能是一个好主意,因为 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
如果转换list
到dict
“聪明”的方式,它更慢(因为你遍历字符串两次)
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_elements
与collections.Counter
_count_elements
内部使用_count_elements
)
d = {}
_count_elements(d, s)
=> 7.5814381919999505
collections.Counter
:) 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_chars
和char_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
最终会比没有它慢。
我在莎士比亚全集的前缀上运行了上述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.