【问题标题】:Quickest Way to Pull A Value Out of an Array?从数组中提取值的最快方法?
【发布时间】:2020-04-19 10:17:04
【问题描述】:

我在 elasticsearch 中有一组 11 毫米的文档,每个文档都有一个标识符数组。每个标识符都是一个包含类型、值和日期的字典。这是一个示例记录:

{
  "name": "Bob",
  "identifiers": [
    {
      "date": "2019-01-01",
      "type": "a",
      "value": "abcd"
    },
    {
      "date": "2019-01-01",
      "type": "b",
      "value": "efgh"
    }
  ]
}

我需要每晚将这些记录传输到 parquet 数据存储,其中只有标识符的值保存在数组中。喜欢:

{
  "name": "Bob",
  "identifiers": ["abcd", "efgh"]
}

我通过循环遍历所有记录并展平标识符来做到这一点。这是我的扁平化变压器:

    def _transform_identifier_values(self, identifiers: List[dict]):
        ret = [
            identifier['value']
            for identifier in identifiers
        ]
        return ret

这可行,但速度很慢。有没有更快的方法来做到这一点?可能是我可以利用的本机实现?

编辑:

尝试了 Sunny 的建议。我惊讶地发现原版实际上表现最好。我的假设是itemgetter 的性能会更好。

这是我的测试方法:

import time
from functools import partial
from operator import itemgetter


def main():

    docs = []
    for i in range(10_000_000):
        docs.append({
            'name': 'Bob',
            'identifiers': [
                {
                    'date': '2019-01-01',
                    'type': 'a',
                    'value': 'abcd'
                },
                {
                    'date': '2019-01-01',
                    'type': 'b',
                    'value': 'efgh'
                }
            ]
        })

    start = time.time()
    for doc in docs:
        _transform_identifier_values_original(doc['identifiers'])
    end = time.time()

    print(f'Original took {end-start} seconds')

    start = time.time()
    for doc in docs:
        _transform_identifier_values_getter(doc['identifiers'])
    end = time.time()

    print(f'Item getter took {end-start} seconds')

    start = time.time()
    for doc in docs:
        _transform_identifier_values_partial_lambda(doc['identifiers'])
    end = time.time()

    print(f'Lambda partial took {end-start} seconds')

    start = time.time()
    for doc in docs:
        _transform_identifier_values_partial(doc['identifiers'])
    end = time.time()

    print(f'Partial took {end-start} seconds')


def _transform_identifier_values_original(identifiers):
    ret = [
        identifier['value']
        for identifier in identifiers
    ]
    return ret


def _transform_identifier_values_getter(identifiers):
    return list(map(itemgetter('value'), identifiers))


def _transform_identifier_values_partial_lambda(identifiers):
    flatten_ids = partial(lambda o: list(map(itemgetter('value'), o)))
    return flatten_ids(identifiers)


def _transform_identifier_values_partial(identifiers):
    flatten = partial(map, itemgetter('value'))
    return list(flatten(identifiers))

if __name__ == '__main__':
    main()

结果:

原始耗时 4.6204328536987305 秒

项目获取器耗时 7.186180114746094 秒

Lambda 部分耗时 10.534514904022217 秒

部分耗时 9.07079291343689 秒

【问题讨论】:

  • 小心,通常弹性不会返回 100% 的文件!
  • @Wonka 如果您正在使用扫描,它将返回 100% 的文档。非扫描查询只会返回前 N 个文档。我正在抓取整个索引。

标签: python elasticsearch parquet


【解决方案1】:

您可以尝试使用operator.itemgetter。

from operator import itemgetter
def _transform_identifier_values(self, identifiers: List[dict]):
    return list(map(itemgetter('value'), identifiers))

甚至可以用它制作一个partial function:

from operator import itemgetter
from functools import partial
flatten_ids = partial(lambda o: list(map(itemgetter('value'), o['identifiers'])))
print(flatten_ids(obj))

如果你想避免 lambda,你可以尝试:

flatten = partial(map, itemgetter('value'))
print(list(flatten(obj['identifiers'])))

我很想看看这些结果。

【讨论】:

  • 感谢您的建议。查看我对不同方法及其结果的编辑。
  • 噢,我确信itemgetter 肯定会加快速度。感谢您的验证!
【解决方案2】:

这是我想出的一个解决方案:

def changeJSON(dictionary):
    new_dict = {'name': dictionary['name'], 'identifiers': []}
    for i in dictionary['identifiers']:
        new_dict['identifiers'].append(i['value'])
    return new_dict

此函数将接收一个字典并以您所需的新格式返回该字典。然后,您可以从内置的 json 库中使用 json.dumps() 函数。它接收一个字典列表并将它们转储到一个 json 文件中。

【讨论】:

  • 我当前的解决方案也有效。我正在寻找最快的方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-06-25
  • 1970-01-01
  • 2023-01-26
  • 2016-08-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多