javascript - 在 python 或 javascript 中正确使用 fold 或 reduce 函数来处理从长到宽的数据?

标签 javascript python functional-programming reshape fold

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

我的数据是一个 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"}
]'

我想把它变成这样的东西,其中查询是定义“行”的主键,嵌套对应于“rank”值和“url”字段的唯一“行”:

'[
{ "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 上面的任何人都在处理相同类型的问题,需要在一个级别下 fork 一些键,在另一层下 fork 其他键......

这是我尝试过的:

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)

???

哦不!

最佳答案

我知道这不是您要的折叠式解决方案,但我会使用 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 分组为您想要的字典格式:

>>> 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 中,然后列出保留在结果

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

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 键值对,而不是一个 dict,那么我们就可以将它们全部相交。这意味着我们终于可以使用 reduce,所以让我们试试看:

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

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

当然,这将始终包括 query 作为公共(public)字段,但我们稍后会处理它。 (此外,您希望 otherstuff 成为一个带有一个 dictlist,所以我们会在它周围多加一对括号)。

同时,results和上面一样,只是filterkeys过滤掉了所有的公共(public)字段,而不是过滤掉除rank之外的所有字段> 和 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(一次找到公共(public)键,然后再次提取剩余键)

关于javascript - 在 python 或 javascript 中正确使用 fold 或 reduce 函数来处理从长到宽的数据?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16154847/

相关文章:

javascript - 消息事件内的回调突变

python - Django - 使用 for 循环从列表创建模型字段

python - 截断日期时间对象 pandas

javascript - 有哪些学习 JavaScript 和编程架构的好网站?

javascript - React-native TextInput 值接收对象,但我显然向它传递了一个字符串?

javascript - 使用微时间作为唯一标识符的潜力

javascript - 默认情况下,JavaScript 中的数组真的是 "sparsed"吗?

python - 使用范围的倍数序列

list - 从 lisp 中的列表中评估函数

javascript - 为什么 compose 使用换能器从左到右应用?