3

试着学会像函数式程序员一样思考——我想用我认为是折叠或归约操作来转换数据集。在 R 中,我认为这是一个重塑操作,但我不确定如何翻译这种想法。

我的数据是一个 json 字符串,如下所示:

s = 
'[
{"query":"Q1", "detail" : "cool", "rank":1,"url":"awesome1"},
{"query":"Q1", "detail" : "cool", "rank":2,"url":"awesome2"},
{"query":"Q1", "detail" : "cool", "rank":3,"url":"awesome3"},
{"query":"Q#2", "detail" : "same", "rank":1,"url":"newurl1"},
{"query":"Q#2", "detail" : "same", "rank":2,"url":"newurl2"},
{"query":"Q#2", "detail" : "same", "rank":3,"url":"newurl3"}
]'

我想把它变成这样的东西,其中查询是定义“行”的主键,嵌套了与“排名”值和“网址”字段相对应的唯一“行”:

'[
{ "query" : "Q1",
    "results" : [
        {"rank" : 1, "url": "awesome1"},
        {"rank" : 2, "url": "awesome2"},
        {"rank" : 3, "url": "awesome3"}        
    ]},
{ "query" : "Q#2",
    "results" : [
        {"rank" : 1, "url": "newurl1"},
        {"rank" : 2, "url": "newurl2"},
        {"rank" : 3, "url": "newurl3"},        
    ]}
]'

我知道我可以迭代,但我怀疑有一个功能操作可以进行这种转换,对吧?

也很想知道如何获得更像这样的东西,Version2:

'[
{ "query" : "Q1",
    "Common to all results" : [
        {"detail" : "cool"}
    ],
    "results" : [
        {"rank" : 1, "url": "awesome1"},
        {"rank" : 2, "url": "awesome2"},
        {"rank" : 3, "url": "awesome3"}        
    ]},
{ "query" : "Q#2",
    "Common to all results" : [
        {"detail" : "same"}
    ],
    "results" : [
        {"rank" : 1, "url": "newurl1"},
        {"rank" : 2, "url": "newurl2"},
        {"rank" : 3, "url": "newurl3"}        
    ]}
]'

在第二个版本中,我想将所有在同一个查询下重复的数据放入一个“其他东西”容器中,其中“排名”下唯一的所有项目都在“结果”容器中。

我正在处理 mongodb 中的 json 对象,并且可以使用 python 或 javascript 来尝试这种转换。

任何建议,例如此转换的正确名称,可能是在大型数据集上执行此操作的最快方法,都值得赞赏!

编辑

在下面结合@abarnert 的优秀解决方案,试图让我的 Version2 上面的任何其他人处理相同类型的问题,需要在一个级别下分叉一些键,在另一个下分叉其他键......

这是我尝试过的:

from functools import partial
groups = itertools.groupby(initial, operator.itemgetter('query'))
def filterkeys(d,mylist):
    return {k: v for k, v in d.items() if k in mylist}

results = ((key, map(partial(filterkeys, mylist=['rank','url']),group)) for key, group in groups)
other_stuff = ((key, map(partial(filterkeys, mylist=['detail']),group)) for key, group in groups)

???

不好了!

4

1 回答 1

2

我知道这不是您所要求的折叠式解决方案,但我会使用 来执行此操作itertools,它的功能相同(除非您认为 Haskell 的功能不如 Lisp……),而且可能是最 Pythonic 的解决方法这。

这个想法是将您的序列视为一个惰性列表,并对其应用一系列惰性转换,直到您获得所需的列表。

这里的关键步骤是groupby

>>> initial = json.loads(s)
>>> groups = itertools.groupby(initial, operator.itemgetter('query'))
>>> print([key, list(group) for key, group in groups])
[('Q1',
  [{'detail': 'cool', 'query': 'Q1', 'rank': 1, 'url': 'awesome1'},
   {'detail': 'cool', 'query': 'Q1', 'rank': 2, 'url': 'awesome2'},
   {'detail': 'cool', 'query': 'Q1', 'rank': 3, 'url': 'awesome3'}]),
 ('Q#2',
  [{'detail': 'same', 'query': 'Q#2', 'rank': 1, 'url': 'newurl1'},
   {'detail': 'same', 'query': 'Q#2', 'rank': 2, 'url': 'newurl2'},
   {'detail': 'same', 'query': 'Q#2', 'rank': 3, 'url': 'newurl3'}])]

只需一步,您就可以看到我们已经有多近了。

要重组每个键,请将 pair 分组为您想要的 dict 格式:

>>> groups = itertools.groupby(initial, operator.itemgetter('query'))
>>> print([{"query": key, "results": list(group)} for key, group in groups])
[{'query': 'Q1',
  'results': [{'detail': 'cool',
               'query': 'Q1',
               'rank': 1,
               'url': 'awesome1'},
              {'detail': 'cool',
               'query': 'Q1',
               'rank': 2,
               'url': 'awesome2'},
              {'detail': 'cool',
               'query': 'Q1',
               'rank': 3,
               'url': 'awesome3'}]},
 {'query': 'Q#2',
  'results': [{'detail': 'same',
               'query': 'Q#2',
               'rank': 1,
               'url': 'newurl1'},
              {'detail': 'same',
               'query': 'Q#2',
               'rank': 2,
               'url': 'newurl2'},
              {'detail': 'same',
               'query': 'Q#2',
               'rank': 3,
               'url': 'newurl3'}]}]

但是等等,还有那些额外的字段是你想要去掉的。简单的:

>>> groups = itertools.groupby(initial, operator.itemgetter('query'))
>>> def filterkeys(d):
...     return {k: v for k, v in d.items() if k in ('rank', 'url')}
>>> filtered = ((key, map(filterkeys, group)) for key, group in groups)
>>> print([{"query": key, "results": list(group)} for key, group in filtered])
[{'query': 'Q1',
  'results': [{'rank': 1, 'url': 'awesome1'},
              {'rank': 2, 'url': 'awesome2'},
              {'rank': 3, 'url': 'awesome3'}]},
 {'query': 'Q#2',
  'results': [{'rank': 1, 'url': 'newurl1'},
              {'rank': 2, 'url': 'newurl2'},
              {'rank': 3, 'url': 'newurl3'}]}]

剩下要做的就是调用json.dumps而不是调用print.


对于您的后续行动,您希望在每一行中获取所有相同的值,query并将它们分组到otherstuff中,然后列出results.

因此,对于每个组,首先我们要获取公共密钥。我们可以通过迭代组中任何成员的键来做到这一点(不在第一个成员中的任何内容都不能在所有成员中),所以:

def common_fields(group):
    def in_all_members(key, value):
        return all(member[key] == value for member in group[1:])
    return {key: value for key, value in group[0].items() if in_all_members(key, value)}

或者,或者……如果我们将每个成员变成一个set键值对,而不是一个字典,那么我们就可以intersect把它们都变成一个。这意味着我们终于可以使用 了reduce,所以让我们尝试一下:

def common_fields(group):
    return dict(functools.reduce(set.intersection, (set(d.items()) for d in group)))

dict我认为 and 之间的来回转换set可能会降低可读性,这也意味着您的值必须是可散列的(对于您的样本数据来说不是问题,因为这些值都是字符串)......但它肯定更简洁。

当然,这将始终query作为一个公共字段包含在内,但我们稍后会处理它。(另外,你想otherstuff成为一个listwith one dict,所以我们会在它周围加上一对额外的括号)。

同时,results与上面相同,只是filterkeys过滤掉所有公共字段,而不是过滤掉除rankand之外的所有内容url。把它放在一起:

def process_group(group):
    group = list(group)
    common = dict(functools.reduce(set.intersection, (set(d.items()) for d in group)))
    def filterkeys(member):
        return {k: v for k, v in member.items() if k not in common}
    results = list(map(filterkeys, group))
    query = common.pop('query')
    return {'query': query,
            'otherstuff': [common],
            'results': list(results)}

所以,现在我们只使用那个函数:

>>> groups = itertools.groupby(initial, operator.itemgetter('query'))
>>> print([process_group(group) for key, group in groups])
[{'otherstuff': [{'detail': 'cool'}],
  'query': 'Q1',
  'results': [{'rank': 1, 'url': 'awesome1'},
              {'rank': 2, 'url': 'awesome2'},
              {'rank': 3, 'url': 'awesome3'}]},
 {'otherstuff': [{'detail': 'same'}],
  'query': 'Q#2',
  'results': [{'rank': 1, 'url': 'newurl1'},
              {'rank': 2, 'url': 'newurl2'},
              {'rank': 3, 'url': 'newurl3'}]}]

这显然不像原始版本那么微不足道,但希望这一切仍然有意义。只有两个新技巧。首先,我们必须迭代groups多次(一次找到公共键,然后再次提取剩余的键)

于 2013-04-22T19:30:59.497 回答