【问题标题】:Memory efficient way of keeping track of unique entries跟踪唯一条目的内存有效方式
【发布时间】:2019-09-03 15:35:36
【问题描述】:

我有大约 50GB 的文件夹,里面装满了文件。每个文件由一行又一行的 JSON 数据组成,在这个 JSON 结构中是一个user_id 的字段。

我需要计算所有文件中唯一用户 ID 的数量(并且只需要总数)。计算这些的最节省内存和相对快速的方法是什么?

当然,将所有内容加载到一个巨大的列表中可能不是最佳选择。我尝试了熊猫,但花了很长时间。然后我尝试将 ID 简单地写入文本文件,但我想我会发现我是否可能遗漏了一些更简单的东西。

【问题讨论】:

  • 您是否尝试过使用set 而不是list?这可能是最有效的方式...
  • 一个集合可能不比列表更适合内存,尽管查找当然更快。由于唯一性限制,这不是上面的重复 - 它不像只是逐行阅读那么简单。
  • 不知道合不合适
  • 然而列表占用更少的内存(虽然它们很慢)

标签: python


【解决方案1】:

Since it was stated that the JSON context of user_id does not matter,我们只是将 JSON 文件视为纯文本文件。

GNU 工具解决方案

我根本不会使用 Python,而是依赖 GNU 提供的工具和管道:

cat *.json | sed -nE 's/\s*\"user_id\"\s*\:\s*\"([0-9]+)\"\s*/\1/p' | sort -un --parallel=4 | wc -l
  • cat *.json: 将所有文件的内容输出到标准输出
  • sed -nE 's/\s*\"user_id\"\s*\:\s*\"([0-9]+)\"\s*/\1/p':查找包含"user_id": "{number}" 的行,仅将数字打印到标准输出
  • sort -un --parallel=4:对输出进行数字排序,忽略重复项(即仅输出唯一值),使用多个 (4) 个作业,并输出到标准输出
  • wc -l: 统计行数,输出到标准输出

要确定这些值是否唯一,我们只需对它们进行排序。您可以通过指定更多的并行作业来加快排序速度,具体取决于您的核心数量。

Python 解决方案

如果您仍然想使用 Python,我建议您使用 setre(正则表达式)

import fileinput
import re

r = re.compile(r'\s*\"user_id\"\s*\:\s*\"([0-9]+)\"\s*')

s = set()
for line in fileinput.input():
    m = r.match(line)
    if m:
        s.add(m.groups()[0])

print(len(s))

使用python3 <scriptname>.py *.json 运行它。

【讨论】:

  • 我怀疑 OP 是否像他认为的那样受到内存限制,但如果他是 Python 解决方案,通过将 s.add(m.groups()[0]) 更改为 s.add(int(m.groups()[0])) 将持续更长时间,因为整数对象比字符串小,并且字符串随着每个连续的字符而增长,而 int 直到它的内部表示大于平台在它使用的任何内部 C 整数类型中可以处理的内容时才会知道(我相当肯定,但对最后一条语句并不肯定)。
  • sed -nE s/.*\"user_id\"\: \"([0-9]+)\".*/\1/p' 不可靠,因为它无法正确解析 JSON。
  • 我认为编辑应该涵盖用例。这并不意味着以一般方式解析 JSON。
【解决方案2】:

先尝试最简单的方法。

编写一个函数 get_user_ids(filepath),它在 JSON 文件中返回 user_id 的列表。

然后做:

from pathlib import Path
the_folder = Path("path/to/the/folder")
user_ids = set()
for jsonpath in the_folder.glob('*.json'):
    user_ids.update(get_user_ids(jsonpath))
print(len(user_ids))

【讨论】:

    【解决方案3】:

    如果用户 ID 列表太大以至于无法合理地放入内存中的集合中,那么一种简单且节省内存的重复数据删除方法是在空目录中简单地创建以用户 ID 命名的文件,然后统计目录中的文件数。这很有效,因为大多数文件系统在索引目录中的文件名方面都很有效。

    import os
    os.chdir('/')
    os.mkdir('/count_unique')
    os.chdir('/count_unique')
    # change the following demo tuple to a generator that reads your JSON files and yields user IDs
    for user_id in 'b', 'c', 'b', 'a', 'c':
        open(user_id, 'w').close()
    print(sum(1 for _ in os.scandir('/count_unique')))
    

    这个输出:3

    【讨论】:

      【解决方案4】:

      由于您只需要user_ids,因此加载.json(作为数据结构),提取任何ids,然后销毁对该结构及其任何部分的所有引用,以便对其进行垃圾收集。

      为了加快进程,你可以在几个进程中并行执行,看看multiprocessing.Pool.map

      【讨论】:

        猜你喜欢
        • 2013-05-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-08-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多