【问题标题】:How can I append lists to a list efficiently?如何有效地将列表附加到列表中?
【发布时间】:2021-11-14 10:52:44
【问题描述】:

我尝试将filtered_sentence 附加到列表wiki_train_lst。我发现删除stop_words的步骤很快,但是删除common_name很慢(可能是common_name的字太多)。如何快速过滤掉stop_wordscommon_name?另外wiki_train_lst要追加的总内容大约是416,000条,这使得追加过程很慢:如何优化呢?

from nltk.tokenize import RegexpTokenizer

wiki_train_lst = []

for text in wiki_train_df.original_text:

    tokenizer = RegexpTokenizer(r'\w+')
    tokenizer = tokenizer.tokenize(text)

    #print(word_tokens)

    filtered_sentence = [w.lower() for w in tokenizer if not w.lower() in stop_words] #remove stop words

    #filtered_sentence = [w for w in filtered_sentence if not w in common_surname_lst or not w in common_name_lst]

    filtered_sentence = [w for w in filtered_sentence if not w in common_name_lst] #remove common names

    filtered_sentence = [w for w in filtered_sentence if w.isalpha()] #remove non alphabatics words
    
    wiki_train_lst.append(filtered_sentence)

    #print(filtered_sentence)

wiki_train_lst

【问题讨论】:

  • 如果您从使用列表推导转换为生成器,是否有帮助?也就是说,将您的[... for ... in ... if... ] 换成(... for ... in ... if...)?这将延迟计算,直到您对其进行具体化。
  • 一种可能的优化是使用集合而不是列表,因为集合具有更快的成员资格测试

标签: python-3.x list dataframe append nltk


【解决方案1】:

我会这样做:

from nltk.tokenize import RegexpTokenizer
from itertools import chain

tokenizer = RegexpTokenizer(r'\w+')

filter_words = set(chain(stop_words, common_name_lst,  common_surname_lst)

def tokenize_text(txt):  # Returns a generator
    tokenized = tokenizer.tokenize(text)
    return (w.lower() for w in tokenized if filter_word(w))

def filter_word(word: str) -> bool:  
    w = word.lower()
    return (w not in filter_words and w.isalpha())

training_list = list(chain.fromiterable((tokenize_text(t) for t in wiki_train_df.original_text))

这利用了几个改进(虽然我没有测试过,所以可能有一两个错误):

  • 将每个文本作为生成器而不是其自己的列表。这意味着在将值添加到最终列表时计算值,从而节省内存分配时间。使用 itertools chain() 函数来展平列表。
  • 预先计算一组要过滤掉的所有单词。在集合中查找值是 O(1),而在列表中查找是 O(n) - 查找列表会遍历每个值,直到找到该值或耗尽列表。不过,集合由哈希表支持,因此这种预先计算可以节省每次迭代的时间。

【讨论】:

  • 你应该能够使用key in dictionary 进行 O(1) 查找,如果我们对性能很挑剔的话,这可以避免查找 get 的方法。
  • 为什么是 dict 而不是 set?
  • 另外,既然你导入了链,为什么不也用它来创建字典呢?这样就不会创建不必要的丢弃新列表
  • 我注意到你也可以使用一套。大多数情况下我都使用过这个,在键上存储一个 bool 不是我正在做的,而是存储一些更复杂的东西来做出决定。也就是说,这种情况下真的只需要一套。
  • @Copperfield 我已经更新了结果,但我认为关键的一点是一点预计算——不管你怎么做——才是关键。优化预计算可能会产生较低的回报。尽管如此,我已经将响应减少到只使用一个集合。
【解决方案2】:

加快速度的一种方法是将所有列表表达式合并为一个:

def my_filter(w):
    w_lower = w.lower()
    if w_lower in stop_words:
        return False
    if w_lower in common_surname_lst 
        return False
    if w_lower in common_name_lst:
        return False
    if not w.isalpha()
        return False
    return True

filtered_sentence = [w.lower() for w in tokenizer if my_filter(w)]

wiki_train_lst.append(filtered_sentence)

请注意,这会增加函数查找的开销,您可以将函数重写为一堆 and 语句:

filtered_sentence = [w for w in tokenizer if w.lower() not in stop_words 
                                              and w.lower() not in common_surname_lst 
                                              and w.lower() not in common_name_lst 
                                              and w.isalpha()]

现在我们有一堆w.lowers(),让我们做点什么:我们可以使用生成器表达式,它类似于列表推导式,但很懒:

filtered_sentence = [w for w in (w.lower() for w in tokenizer) if w not in stop_words 
                                                                   and w not in common_surname_lst 
                                                                   and w not in common_name_lst 
                                                                   and w.isalpha()]

使用filter 可能更好:

filtered_sentence = filter(tokenizer, my_filter)

要提高common_name 搜索的速度,请先通过上述方法之一将列表转换为set

common_name_lst = set(common_name_lst)

其余代码可以保持不变,除非您想重命名变量以使类型更清晰。

最终,如果您需要性能,CPython 通常是次优选择。有一些方法可以让它更快(参见PyPyCython),但你最好用更容易优化的语言重写代码。

【讨论】:

  • 非常感谢,帮了大忙!
猜你喜欢
  • 2015-10-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多