一般递归jq,用于求和在任何级别上找到的所有对象值



我看到映射数组中的求和值,我想知道jq中是否有一种优雅的方法可以对一组对象执行一般递归,并对它们包含的所有值求和,以生成一个输出对象作为所有其他对象的和。

如果在一个包裹所有其他对象的单个输入对象上更容易做到这一点(以避免混淆),那也没关系。

  • 在一系列模糊的独立对象上递归,这些对象有很多任意嵌套(任意的意思是,如果通用递归可以满足我的要求,我不想为对象层次结构的细节编写jq代码),并且
  • 求和键的每个数值:在任何级别找到的值对

每个对象都可以是稀疏的,但所有重合的对象关键路径都应该将其值相加到单个输出对象层次结构中的相同位置。

例如(将一个数组围绕这3个对象,如果这样做更容易的话):

{ A { B { x:1, y:4      }, C { x:3,      z:6 }, t:2 }, s:5 }
{ A { B { x:2     , z:3 },                      t:1 }      }
{ A {                    , C {      y:3, z:2 }      }, s:3 }

将产生

{ A { B { x:3, y:4, z:3 }, C { x:3, y:3, z:8 }, t:3 }, s:8 }

TL:DR在这些对象的数组上执行. as $dot | reduce ([$dot[] | paths(numbers)] | unique)[] as $path ({}; setpath($path; [$dot[] | getpath($path)] | add))


由于您的示例输入不是有效的JSON,下面是我用于测试的JSON。如果这与您的不匹配,请告诉我:

[
    {"a": {"b": 1, "c": 2},         "e": 3},
    {"a": {        "c": 2, "d": 3},         "f": 4}
]

首先,我们将研究如何在两个对象之间执行这种"加法合并"操作;之后,应该可以很容易地在一组对象上执行它。

我们将把我们的函数定义为additive_merge($xs; $ys)。首先,我们将获得每个对象上的所有路径的列表,其中有数字,并删除重复项:

([$xs, $ys | paths(numbers)] | unique) as $paths

然后,我们将减少这个路径列表,以一个空对象作为初始状态,使用setpath将空对象中的每个路径设置为$xs$ys上这些路径的值之和:

reduce $paths[] as $path ({}; setpath($path; [$xs, $ys | getpath($path)] | add))

现在,我们有了additive_merge函数:

def additive_merge($xs;$ys): reduce ([$xs, $ys | paths(numbers)] | unique)[] as $path ({}; setpath($path; [$xs, $ys | getpath($path)] | add));

如果我们想在数组上运行这个函数,我们可以减少这个函数上的数组:

reduce .[] as $x ({}; additive_merge(.; $x))

或者修改函数,使其在一组对象上工作,这实际上非常容易;只需使用点输入,将其保存到一个变量中,并在上一个函数中使用$xs, $ys的任何位置对其进行解压缩

def additive_merge: . as $dot | reduce ([$dot[] | paths(numbers)] | unique)[] as $path ({}; setpath($path; [$dot[] | getpath($path)] | add));

希望这能有所帮助!

这里有一个解决方案,它使用来流选择减少设置路径

reduce (tostream|select(length==2)|.[0] = .[0][1:]) as [$p,$v] (
   {};
   setpath($p; getpath($p) + $v)
)

最新更新