【问题标题】:Get first letter with maximum occurence of a string获取字符串出现次数最多的第一个字母
【发布时间】:2017-01-04 17:12:17
【问题描述】:

我想获取字符串出现次数最多的第一个字母。

例如:

 "google" -> g  
 "azerty" -> a  
 "bbbaaa" -> b

我已经有一个工作代码,使用OrdererDict() 来避免自动键重新排列:

from collections import OrderedDict

sentence = "google"

d = OrderedDict()

for letter in sentence:
    if letter not in d.keys():
        d[letter] = sentence.count(letter)

print(max(d, key=d.get)) # g

但我正在寻找一种可能的单行或更优雅的解决方案(如果可能的话)。

注意: 我已经尝试使用Counter(),但它不起作用,因为 python 中的 dict 不记得插入键的顺序。

例如

from collections import Counter

sentence = "bbbaaa"

c = Counter(sentence)
print(c.most_common()[0][0]) # have 50% chances of printing 'a' rather than 'b'.

额外问题:有人能解释一下为什么 OrderedDict() 不是 python 中的默认字典行为吗?

【问题讨论】:

  • OrderedDict() 比 dict 慢。
  • 为什么需要 OrderedDict?如果您想确保键不会被覆盖,请使用the setdefault method or a DefaultDict,对于“单线”,您可以将循环简化为理解
  • goooogleaaabbbb 想要的结果是什么?
  • @MaxChrétien 是由单词中第一个出现的那个或词典中第一个出现的那个打破的平局。

标签: python string python-3.x dictionary


【解决方案1】:

可能不是很快,但单线!

>>> s = "aaabbbbc"
>>> sorted(s, key=lambda c: (-s.count(c), s.index(c)))[0]
'b'

编辑

感谢@Ohad Eytan 的评论,甚至更短:

>>> min(s, key=lambda c: (-s.count(c), s.index(c)))
'b'

基准测试

今晚感觉很无聊,所以我基准测试(使用timeit)测试@Joohwan 的most_common_char() 解决方案(mostcc)、@Blender 的OrderedCounter 解决方案(odict)和我自己的一个班轮解决方案(onelin,使用min变体)。最快的解决方案始终是 mostcc:对于包含几个不同字符的长字符串,比 onelin 快 10 倍,对于非常短的字符串,比 odict 快 4 倍。对于短字符串或重复字符很少的字符串,onelin 胜过 odict(否则,反之)。以下是详细信息(长度=字符串的长度,#chars=为每个字符随机选择的不同 unicode 字符数,mostcc=执行 10,000 次 mostcc 的时间,odict=odict 与 mostcc 相比多长时间,onelin= oneline 比 mostcc 长了多少)。

Length  #chars  mostcc odict  onelin
10      10:     0.08s  3.76x  1.61x
10      100:    0.10s  3.57x  1.27x
10      1000:   0.12s  3.12x  1.34x
100     10:     0.43s  1.96x  3.29x
100     100:    0.59s  2.16x  2.18x
100     1000:   0.80s  1.92x  1.72x
1000    10:     3.48s  1.56x  9.79x
1000    100:    3.44s  1.72x  6.43x
1000    1000:   6.55s  1.68x  3.30x

【讨论】:

  • 为什么不min(s, key=lambda c: (-s.count(c), s.index(c)))
  • 不错!我什至不知道min() 有一个key 参数。更新我的答案。
  • 首先,感谢这个基准!由于这个主题现在是一个速度测试,您能否通过用于输出这些结果的代码,对于新挑战者来说可能会很好。否则,通过向后迭代对@Joohwan 玩得很好!我最喜欢的答案仍然是 OdererCounter,因为它最容易理解,但性能仍然不错。
  • 很高兴你喜欢这个基准。不幸的是,我只是在控制台中执行此操作,我没有保存代码。基本上我为每个函数创建了一个函数,然后我为每个函数在各种长度和#chars 的随机字符串上运行timeit.timeit()。类似:t = timeit.timeit("mostcc(s)", number=10000, globals={"mostcc":mostcc, "s": s})。为了生成随机字符串,我做了这样的事情:"".join([random.randint(32, n_chars+32) for i in range(length)].
【解决方案2】:

我知道您想要一个单行,但是如果您必须多次重复此任务或处理非常长的句子怎么办?我不知道这里的确切用例,但考虑到算法的空间和时间复杂度,值得您花时间。

例如,在您的解决方案中,您使用sentence.count() 迭代句子的次数超过了必要的次数,这需要O(n * number of unique characters)。之后,您再次遍历 ordereddict 以找到最大值(另一个 O(number of unique characters) 操作)。

在公认的解决方案中,我们最终不得不定义一个新类(顺便说一句,它打破了您的 1 线要求)并使用大量样板代码和功能实例化新对象,您可能每次都不需要执行您的任务。

如果你不介意多写几行代码(我知道这不是问题要问的),我们可以构建一个可重用函数,它只需要遍历字符串 once 并使用恒定且最小的空间:

from collections import defaultdict


def most_common_char(sentence):
    if not sentence:
        return ''

    max_count = 1
    max_char = sentence[-1]
    char_counts = defaultdict(int)
    char_counts[max_char] = 1

    for i in xrange(len(sentence) - 2, -1, -1):
        char = sentence[i]
        char_counts[char] += 1
        if char_counts[char] >= max_count:
            max_count = char_counts[char]
            max_char = char

    return max_char

我们使用最大计数来跟踪字符因为我们遍历字符串并在迭代结束时将其吐出。请注意,我们会向后迭代,因为您想要先出现的字母(即最后更新的获胜)。

【讨论】:

  • 大多数时候,我更喜欢尽可能使用官方库。它使代码更清晰,错误更少。但我明白你的意思,就复杂性而言,我的解决方案并不是最好的。我不确定验证答案。明天我将对所有答案进行基准测试,以比较哪个是最好的。无论如何,感谢您的宝贵时间。
  • @Joohwan 我对一些解决方案进行了基准测试,您的解决方案大获全胜(有关详细信息,请参阅我的答案)。 :)
  • @Joohwan,在尝试缩小您的功能(我失败了^^')之后,我仍然删除了 4 行。说明:使用for char in reverse(sentence) 代替for i in xrange(len(sentence) - 2, -1, -1):。这允许您删除char = sentence[i],因为您已经获得了char。因为现在你从字符串的最后一个字符开始你可以删除第一个 char_counts[max_char] = 1 并替换 max_char = sentence[-1]by max_char = '' ,这样你就可以删除if not sentence: return ''
【解决方案3】:

您可以使用Counter()next() 一起查找满足条件的第一个字母:

>>> s = "google"
>>> c = Counter(s)
>>> next(x for x in s if c[x] == c.most_common(1)[0][1])
'g'

【讨论】:

  • 因为您有c.most_common(1),它将返回最常见的元素之一,但不会返回具有相同计数的其余元素。所以next(x for x in s if c[x] == c.most_common(1)[0][1]) 的整个谓词遭受相同的随机返回...
  • @dawg 不,不是。 c.most_common(1)[0][1] 将只提供所需的 ,而且根本不是随机的
【解决方案4】:

您还可以通过将结果列表按各种属性排序来解决您在问题末尾描述的关于使用 Counter 的问题:首先是计数,其次是像这样的字典顺序:

from collections import Counter

sentence = "google"

c = Counter(sentence)
print(sorted(c.most_common(), key = lambda x: (-x[1], sentence.index(x[0]))))

输出:

=> [('g', 2), ('o', 2), ('l', 1), ('e', 1)]

只是为了好玩:

高尔夫版

# If your sentence is s:
print(sorted(collections.Counter(s).most_common(),key=lambda x:(-x[1],s.index(x[0]))))

【讨论】:

    【解决方案5】:

    collections.OrderedDict 的文档实际上有 a recipe for an OrderedCounter

    In [5]: from collections import Counter, OrderedDict
    
    In [6]: class OrderedCounter(Counter, OrderedDict):
       ...:     pass
       ...:
    
    In [7]: OrderedCounter("google").most_common()[0][0]
    Out[7]: 'g'
    

    【讨论】:

    • 该死的我因为没有完整地阅读文档而变得愚蠢。无论如何,谢谢,这正是我想要的
    猜你喜欢
    • 2011-04-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-28
    • 2021-10-08
    • 2017-11-12
    • 1970-01-01
    相关资源
    最近更新 更多