繁体   English   中英

如何有效地获取 python 中列表列表中项目的计数

[英]How to efficiently get count for item in list of lists in python

我有如下三个列表。

mylist = [[5274919, ["my cat", "little dog", "fish", "rat"]], 
          [5274920, ["my cat", "parrot", "little dog"]], 
          [5274991, ["little dog", "fish", "duck"]]] 
myconcepts = ["my cat", "little dog"]
hatedconcepts = ["rat", "parrot"]

对于myconcepts中的每个概念,我想使用mylist计算与之相关的所有其他概念。 然后从中删除hatedconcepts的概念。 所以,我的 output 应该如下所示。

{"my cat": [("my cat", 2), ("little dog", 2), ("fish", 1)],
"little dog": [("little dog", 3), ("my cat", 2), ("fish", 2), ("duck", 1)]}

我正在使用这段代码来做到这一点。

import collections
myoutput = []
for concept in myconcepts:
    mykeywords = []

    for item in mylist:
        if concept in item[1]:
            for mykeyword in item[1]:
                if mykeyword in hatedconcepts:
                    pass
                else:
                    mykeywords.append(mykeyword)
    if len(mykeywords) > 0:            
        sorted_keywords = collections.Counter(mykeywords).most_common()
        myoutput.append(tuple((concept, sorted_keywords)))
print(myoutput)

代码的output为:

[('my cat', [('my cat', 2), ('little dog', 2), ('fish', 1)]), ('little dog', [('little dog', 3), ('my cat', 2), ('fish', 2), ('duck', 1)])]

但是现在我有一个巨大的mylist ,大小为 3GB 和近 9000 myconcepts hatedconcepts计数只有 20 个。看起来使用我当前的代码运行大约需要两周时间。 主要原因可能是我当前的程序是O^3 ,效率不高。 因此,我正在寻找使我当前的程序更有效率的方法。 我什至可以接受甚至需要 5-6 天才能运行的 pythonic 解决方案。 请让我知道你的想法。

我在以下位置添加了一部分mylisthttps://drive.google.com/file/d/1M3EhIRwwKwD3Kv4zDsmXaH1D73tx0eF3/view?usp=sharing只是为了了解它的外观。

如果需要,我很乐意提供更多详细信息。

正如其他评论和答案所表明的那样,这个操作最好由 Spark 或数据库处理。 也就是说,这是我的看法,我引入了一些集合操作并最小化了重复循环。

from collections import defaultdict

def get_counts(lst, concepts, hated_concepts):
    result = {concept: defaultdict(int) for concept in concepts}

    concepts_set = set(concepts)
    hated_concepts_set = set(hated_concepts)

    for _, inner_list in lst:
        # ignore hated concepts
        relevant = set(inner_list).difference(hated_concepts_set)

        # determine which concepts need to be updated
        to_update = relevant.intersection(concepts_set)

        for concept in to_update:
            for word in relevant:
                result[concept][word] += 1

    return result

Output 在下方。 您提到 output “必须排序”,但我不清楚所需的排序是什么。 一些计时测试表明这比您在样本数据上提供的代码快 9 倍。

{
    'my cat': defaultdict(<class 'int'>, {'my cat': 2, 'fish': 1, 'little dog': 2}), 
    'little dog': defaultdict(<class 'int'>, {'my cat': 2, 'fish': 2, 'little dog': 3, 'duck': 1})
}

性能改进

emj_functn avg 0.9355s
get_counts avg 0.1141s

性能测试脚本:

import random
import string
import time

words = list({
    ''.join(random.choice(string.ascii_lowercase) for _ in range(5))
    for _ in range(1000)
})
test_list = [[random.randint(1e6, 1e7), [random.choice(words) for _ in range(100)]] for _ in range(1000)]
test_concepts = [random.choice(words) for _ in range(100)]
test_hated_concepts = [random.choice(words) for _ in range(50)]


def emj_functn(lst, concepts, hated_concepts):
    ...


def get_counts(lst, concepts, hated_concepts):
    ...


TEST_CASES = 10

start_time = time.time()
for _ in range(TEST_CASES):
    emj_functn(test_list, test_concepts, test_hated_concepts)
end_time = time.time()
avg = (end_time - start_time) / TEST_CASES
print(f'emj_functn avg {avg:.4}s')

start_time = time.time()
for _ in range(TEST_CASES):
    get_counts(test_list, test_concepts, test_hated_concepts)
end_time = time.time()
avg = (end_time - start_time) / TEST_CASES
print(f'get_counts avg {avg:.4}s')

我试图让它变快,避免一些重复的循环。 请检查这是否加快了速度。

from itertools import chain
from collections import Counter, defaultdict

database = defaultdict(set)
output = {}

# created a map for different concepts, so we only search the indices where a certain concept is
for index, (_, concepts) in enumerate(mylist):
    for concept in concepts:
        database[concept].add(index)

for concept in myconcepts:
    search_indices = database[concept]
    all_counts = Counter(chain.from_iterable(mylist[i][1] for i in search_indices))
    for hc in hatedconcepts:
        if hc in all_counts: all_counts.pop(hc)
    output[concept] = sorted(all_counts.items(), key=lambda x: x[1], reverse=True)

试试这个:

from collections import Counter
req={}
for i in myconcepts:
    x=sum([j[1] for j in mylist if i in j[1]],[])
    x=[i for i in x if i not in hatedconcepts]
    req[i]=dict(Counter(x))
print(req)

output:

{'my cat': {'my cat': 2, 'little dog': 2, 'fish': 1}, 'little dog': {'my cat': 2, 'little dog': 3, 'fish': 2, 'duck': 1}}

如果您有类似字数统计的示例,我建议您使用Apache SparkApache Hadoop ,事实上,这些框架专注于此。

两者都有框架可以与 python 一起使用。

但是如果你只想坚持使用 python。

我建议并行化:

my_list拆分为n个子列表my_sub_lists

my_list = ["my cat", "little dog", "fish", "rat", "my cat","little dog" ]
# split my_list into n=2 sublists
my_sub_lists = [["my cat", "little dog", "fish"], ["rat", "my cat","little dog"]]

并行计算my_sub_lists的项目计数

Process 1: Counter(["my cat", "little dog", "fish"])
Process 2 : Counter("rat", "my cat","little dog"])

你会得到一些中间聚合。 my_sub_counts

my_sub_counts = [{"my cat":1, "little dog":1, "fish":1}, {"rat":1, "my cat":1,"little dog":1}]

合并中间结果以获得最终的项目计数。

result = {"my cat":2, "little dog":2, "fish":1, "rat":1}

合并中间聚合会更容易,因为它会更小。

我意识到这有点晚了,但只是想把我的答案放在那里。

在我写我的之前,我没有注意到bphi的回答。 这个想法几乎相同,但这个答案是有序的。

from collections import Counter, defaultdict

s_myconcepts = set(myconcepts)
s_hatedconcepts = set(hatedconcepts)

myoutput = defaultdict(list)
for _, item in mylist:
    item = set(item)
    for concept in item.intersection(s_myconcepts):
        myoutput[concept].extend(item - s_hatedconcepts)

myoutput = {k: Counter(v).most_common() for k, v in myoutput.items()}

这是一个很好的问题,有一个很好的示例数据集。

通过采用流累加器架构,您应该能够将它的运行时间缩短到几个小时,并且只使用非常少量的 memory。

我注意到子列表的内容非常规则,只是具有重复嵌套结构的列表和本身不包含任何方括号的字符串。 这允许遵循以下策略:

定义一个 chunk_size ,理想情况下它是文件中描述子列表的文本字符数的两倍以上,我使用了 ~2Megs。

将文件中的下一个 chunk_size 个字符入缓冲区。

使用正则表达式.finditer从缓冲区中提取连续的子列表

eval() 每个 sub_list 然后在结果上运行 Accumulator class 实例以根据您的包含/排除规则增加计数。

对缓冲区中的所有子列表重复

通过finditer保存缓冲区末尾未使用的字符

重复文件中的下一个缓冲区,(在前一个缓冲区末尾添加任何未使用的字符)

当文件用完时,从 Accumulator 实例中提取运行总计

我首先找到了在您的 11meg 示例中使用最多的短语,然后使用第 1、第 3 和第 5 个作为要包含的单词的测试列表,第 2、第 4 和第 6 个用于要排除的单词。

我有两种解决问题的方法,因此我可以比较结果以确保它们相符。

  1. 简单:只需读入整个文件,评估它,然后将 Accumulaor 应用于每个子列表
  2. 式传输:如上所述分块。

Simple 解决方案占用 Streaming 的三分之二时间,因此速度更快(当您可以将所有内容放入内存时)。

Streaming 解决方案占用 Simple 解决方案 memory 的 1/150 ,并且应该在 memory 中保持相当稳定,用于更大的文件大小,而 Simple 解决方案将需要更多 memory。

Streaming 解决方案运行 11 兆文件所需的时间不到 5 秒,因此运行 11 兆文件可能需要几个小时。

我打算就此写一篇博客文章,其中将显示我的最终代码。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM