【问题标题】:Fastest object to iterate a chars in a list of strings在字符串列表中迭代字符的最快对象
【发布时间】:2017-05-29 05:28:11
【问题描述】:

我正在遍历单词列表以查找单词之间最常用的字符(即在列表中[hello, hank]'h' 算作出现两次,而 'l' 算作出现一次。)。 python 列表工作正常,但我也在研究 NumPy(dtype 数组?)和 Pandas。看起来 Numpy 可能是要走的路,但还有其他软件包需要考虑吗?还有什么方法可以让这个功能更快?

有问题的代码:

def mostCommon(guessed, li):
    count = Counter()
    for words in li:
          for letters in set(words):
              count[letters]+=1
    return count.most_common()[:10]

谢谢。

【问题讨论】:

  • 你能解释一下“最常见的独特字符”是什么意思吗?并包括一些示例输入和输出数据
  • @Chris_Rands 编辑了一个例子,如果你需要更多,请 lmk。
  • 所以你只想要最频繁的字符还是所有字符的频率?

标签: python pandas numpy optimization


【解决方案1】:

这是使用其views-concept 的 NumPy 方法 -

def tabulate_occurrences(a):           # Case sensitive
    chars = np.asarray(a).view('S1')
    valid_chars = chars[chars!='']
    unqchars, count = np.unique(valid_chars, return_counts=1)
    return pd.DataFrame({'char':unqchars, 'count':count})

def topNchars(a, N = 10):               # Case insensitive
    s = np.core.defchararray.lower(a).view('uint8')
    unq, count = np.unique(s[s!=0], return_counts=1)
    sidx = count.argsort()[-N:][::-1]
    h = unq[sidx]
    return [str(unichr(i)) for i in h]

示例运行 -

In [322]: a = ['er', 'IS' , 'you', 'Is', 'is', 'er', 'IS']

In [323]: tabulate_occurrences(a) # Case sensitive
Out[323]: 
  char  count
0    I      3
1    S      2
2    e      2
3    i      1
4    o      1
5    r      2
6    s      2
7    u      1
8    y      1

In [533]: topNchars(a, 5)         # Case insensitive
Out[533]: ['s', 'i', 'r', 'e', 'y']

In [534]: topNchars(a, 10)        # Case insensitive
Out[534]: ['s', 'i', 'r', 'e', 'y', 'u', 'o']

【讨论】:

  • 嗯。我刚刚计时,看起来比常规列表解决方案要慢。
  • @ZtoYi 您是否也需要前 10 个字符的实际出现次数,还是只想找到这些字符?
  • 只要人物就好了。
  • @ZtoYi 另外,你只处理小写字母吗?
  • 小写和大写应该算同一个字母。
【解决方案2】:

选项 1

def pir1(li):
    sets = [set(s) for s in li]
    ul = np.array(list(set.union(*sets)))
    us = np.apply_along_axis(set, 1, ul[:, None])
    c = (sets >= us).sum(1)
    a = c.argsort()[:-11:-1]
    return ul[a]

选项 2

def pir2(li):
    return Counter(chain.from_iterable([list(set(i)) for i in li])).most_common(10)

假设一个单词列表li

import pandas as pd
import numpy as np
from string import ascii_lowercase

li = pd.DataFrame(
    np.random.choice(list(ascii_lowercase), (1000, 10))
).sum(1).tolist()

包括 Divakar 和 OP 的功能

def tabulate_occurrences(a):
    chars = np.asarray(a).view('S1')
    valid_chars = chars[chars!='']
    unqchars, count = np.unique(valid_chars, return_counts=1)
    return pd.DataFrame({'char':unqchars, 'count':count})

def topNchars(a, N = 10):
    s = np.core.defchararray.lower(a).view('uint8')
    unq, count = np.unique(s[s!=0], return_counts=1)
    sidx = count.argsort()[-N:][::-1]
    h = unq[sidx]
    return [str(chr(i)) for i in h]

def mostCommon(li):
    count = Counter()
    for words in li:
          for letters in set(words):
              count[letters]+=1
    return count.most_common()[:10]

测试

import pandas as pd
import numpy as np
from string import ascii_lowercase
from timeit import timeit

results = pd.DataFrame(
    index=pd.RangeIndex(5, 405, 5, name='No. Words'),
    columns=pd.Index('pir1 pir2 mostCommon topNchars'.split(), name='Method'),
)

np.random.seed([3,1415])
for i in results.index:    
    li = pd.DataFrame(
        np.random.choice(list(ascii_lowercase), (i, 10))
    ).sum(1).tolist()
    for j in results.columns:
        v = timeit(
            '{}(li)'.format(j),
            'from __main__ import {}, li'.format(j),
            number=100
        )
        results.set_value(i, j, v)

ax = results.plot(title='Time Testing')
ax.set_ylabel('Time of 100 iterations')

【讨论】:

  • 那么,最终的 o/p 将是一个最大出现的字符,对吧?
  • @Divakar 根据他们的代码,他们想要前 10 名。还评论其他答案“如果我想要下一个最频繁怎么办?”
  • @piRSquared 我正在尝试在您吐出这些选项时计时!谢谢!您能否以我可以获得前 10 个字符的方式对它们进行排序(关系无关紧要)?谢谢。
  • @ZtoYi 我得跑了。希望我对您有所帮助。祝你好运。
  • 不错的选择,但我不认为(如目前实施的那样)这些满足每个单词只计算每个字母一次的要求
【解决方案3】:

假设您只想要最常见的字符,其中每个字符每个单词只计算一次:

>>> from itertools import chain
>>> l = ['hello', 'hank']
>>> chars = list(chain.from_iterable([list(set(word)) for word in l]))
>>> max(chars, key=chars.count)
'h'

由于 C 级实现,使用 maxlist.count 可能比使用 Counter 快​​很多。

【讨论】:

  • 如果我想要下一个最常见的字母怎么办?
  • @ZtoYi 那么你可能不想使用这种方法;虽然仍有可能,但您可以使用heapq 或对chars 的列表进行排序,但两者都会增加相当多的开销
【解决方案4】:

这看起来已经非常快了,并且在O(n) 中运行。我看到的唯一真正的改进机会是通过将li 拆分为多个部分来并行化这个过程。

【讨论】:

【解决方案5】:

这是一个纯 Python 解决方案,它可以区分每个字符串,加入集合,然后计算结果(使用 Divakar 的示例列表)

>>> li=['er', 'IS' , 'you', 'Is', 'is', 'er', 'IS']
>>> Counter(e for sl in map(list, map(set, li)) for e in sl)
Counter({'I': 3, 'e': 2, 's': 2, 'S': 2, 'r': 2, 'o': 1, 'i': 1, 'u': 1, 'y': 1})

如果想让大小写都算同一个字母:

>>> Counter(e for sl in map(list, map(set, [s.lower() for s in li])) for e in sl)
Counter({'i': 4, 's': 4, 'e': 2, 'r': 2, 'o': 1, 'u': 1, 'y': 1})

现在让我们计时:

from __future__ import print_function
from collections import Counter
import numpy as np
import pandas as pd

def dawg(li):
    return Counter(e for sl in map(list, map(set, li)) for e in sl)

def nump(a):
    chars = np.asarray(a).view('S1')
    valid_chars = chars[chars!='']
    unqchars, count = np.unique(valid_chars, return_counts=1)
    return pd.DataFrame({'char':unqchars, 'count':count})

if __name__=='__main__':
    import timeit  
    li=['er', 'IS' , 'you', 'Is', 'is', 'er', 'IS'] 
    for f in (dawg, nump):
        print("   ",f.__name__, timeit.timeit("f(li)", setup="from __main__ import f, li", number=100) )  

结果:

dawg 0.00134205818176
nump 0.0347728729248

在这种情况下,Python 解决方案明显更快

【讨论】:

    【解决方案6】:

    做事

    counter = Counter(''.join(li))
    most_common = counter.most_common()
    

    你就完成了

    【讨论】:

      猜你喜欢
      • 2012-02-12
      • 2022-01-02
      • 1970-01-01
      • 2016-06-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多