Grouping/Joining 个数组列表的元素按键在一起

Grouping/Joining elements of an Array List together by key

我有以下数据结构作为输入

我想在不丢失任何数据的情况下对数据进行特殊分组

const input =
[
  {
   Id: 10,
   name: "Test_10",
   points: [{…}, {…}, {…}],
   label: "Label_10",
   level: "1",
   unit: "%"
  },
  {
   Id: 20,
   name: "Test_10",
   points: [{…}, {…}, {…}, {…}],
   label: "Label_10",
   level: "1",
   unit: "%"
  },
  {
   Id: 30,
   name: "Test_10",
   points: [{…}, {…}, {…}],
   label: "Label_10",
   level: "2",
   unit: "°C"
  },
  {
   Id: 40,
   name: "Test_10",
   points: [{…}, {…}, {…}, {…}],
   label: "Label_10",
   level: "2",
   unit: "°C"
  }
]

在我的 conversion/grouping 之后,我想在下面有这个输出。我想将所有点信息合并到同一个 "level" 中。所以我的输出中有“1”级七分。但我只希望它用于我的 1 级项目,而不是 2 级项目。有没有简单的方法可以实现这一点? (也许吧,但是对于 ramda 或 rxjs 之外的函数来说是必要的吗?)

const output =
[
  {
   Id: 10, // is not important for further code, can be first entry
   name: "XX", // is not important for further code
   points: [{…}, {…}, {…}, {…}, {…}, {…}, {…}], // all points now together
   label: "Label_10",
   level: "1",
   unit: "%"
  },
  {
   Id: 30,
   name: "Test_10",
   points: [{…}, {…}, {…}],
   label: "Label_10",
   level: "2",
   unit: "°C"
  },
  {
   Id: 40,
   name: "Test_10",
   points: [{…}, {…}, {…}, {…}],
   label: "Label_10",
   level: "2",
   unit: "°C"
  }
]

您可以使用 JavaScript 的 reduce() 方法来做到这一点。

function groupByLevel(level) {
  return input.reduce((arr, currentValue) => {

    if (currentValue.level === level) {
      const index = arr.findIndex(item => item.level === level);

      if (index === -1) {
        arr.push(currentValue);
      } else {
        arr[index].points = arr[index].points.concat(currentValue.points);
      }
    } else {
      arr.push(currentValue);
    }

    return arr;
  }, []);
}

演示:

const input = [{
    Id: 10,
    name: "Test_10",
    points: [1, 2, 3],
    label: "Label_10",
    level: "1",
    unit: "%"
  },
  {
    Id: 20,
    name: "Test_10",
    points: [4, 5, 6, 7],
    label: "Label_10",
    level: "1",
    unit: "%"
  },
  {
    Id: 30,
    name: "Test_10",
    points: [1, 2, 3],
    label: "Label_10",
    level: "2",
    unit: "°C"
  },
  {
    Id: 40,
    name: "Test_10",
    points: [4, 5, 6, 7],
    label: "Label_10",
    level: "2",
    unit: "°C"
  }
]

function groupByLevel(level) {
  return input.reduce((arr, currentValue) => {

    if (currentValue.level === level) {
      const index = arr.findIndex(item => item.level === level);

      if (index === -1) {
        arr.push(currentValue);
      } else {
        arr[index].points = arr[index].points.concat(currentValue.points);
      }
    } else {
      arr.push(currentValue);
    }

    return arr;
  }, []);
}

console.log(groupByLevel("1"));

使用分组和过滤器 演示:

const input = [{
    Id: 10,
    name: "Test_10",
    points: [1, 2, 3],
    label: "Label_10",
    level: "1",
    unit: "%"
  },
  {
    Id: 20,
    name: "Test_10",
    points: [4, 5, 6, 7],
    label: "Label_10",
    level: "1",
    unit: "%"
  },
  {
    Id: 30,
    name: "Test_10",
    points: [1, 2, 3],
    label: "Label_10",
    level: "2",
    unit: "°C"
  },
  {
    Id: 40,
    name: "Test_10",
    points: [4, 5, 6, 7],
    label: "Label_10",
    level: "2",
    unit: "°C"
  }
]


let levelsGroups;

function filterElement(el){
   const topElement = levelsGroups[el.level]
   if(!topElement){
      levelsGroups[el.level] = el;
      return true;
   }
   topElement.points.concat(el.points);   
   return false;
}

function groupInput() {
    levelsGroups = {};
    return input.filter(filterElement);
}
console.log(groupInput());

更新

我对下面的原始解决方案不是很满意。这个版本不包含任何依赖项,如果不是 crystal-clear,它是一个相当简单的 ES6 解决方案:

const combine = (a, b) =>
  ({...b, points: a .points .concat (b .points) })
 
const mergeLevel1Points = xs => 
  [xs .filter (x => x .level == '1') .reduce (combine, {points: []}), ...xs .filter(x => x .level != '1')]

 
const input = [
  {Id: 10, name: "Test_10", points: [1, 2, 3], label: "Label_10", level: "1", unit: "%"},
  {Id: 20, name: "Test_10", points: [4, 5, 6, 7], label: "Label_10", level: "1", unit: "%"},
  {Id: 30, name: "Test_10", points: [8, 9], label: "Label_10", level: "2", unit: "°C"},
  {Id: 40, name: "Test_10", points: [10, 11, 12], label: "Label_10", level: "2", unit: "°C"}
]
 
console .log (
  mergeLevel1Points (input)
)

它保留最后一个而不是第一个 level 1 元素的数据。我确信这可以很容易地改变,但问题清楚地表明它并不重要,所以我保留了实施之外的内容。


原解

这是一个 Ramda 解决方案。我不觉得它特别可读,但要求本身很奇怪,所以也许没关系:

const combine = mergeWithKey (
  (key, a, b) => key == 'points' ? concat(a, b) : a
)

const mergeLevel1Points = pipe (
  partition (propEq ('level', '1') ),
  apply (useWith (prepend, [reduce (combine, {})] ))
)  

const input = [
  {Id: 10, name: "Test_10", points: [1, 2, 3], label: "Label_10", level: "1", unit: "%"},
  {Id: 20, name: "Test_10", points: [4, 5, 6, 7], label: "Label_10", level: "1", unit: "%"},
  {Id: 30, name: "Test_10", points: [8, 9], label: "Label_10", level: "2", unit: "°C"},
  {Id: 40, name: "Test_10", points: [10, 11, 12], label: "Label_10", level: "2", unit: "°C"}
]

console .log (
  mergeLevel1Points (input)
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
<script>
const {mergeWithKey, concat, pipe, partition, propEq, apply, useWith, prepend, reduce} = R
</script>

combine 只需要两个项目,主要使用第一个项目,将其 points 与第二个项目组合。也可以这样写:

const combine = (a, b) => ({...a, points: (a.points || []).concat(b.points)})

main 函数首先将输入分为匹配 level 1 和不匹配 (partition) 的那些。然后它使用 combine (reduce(combine, {})) 将第一组折叠成一个值,并将结果添加到第二组。 apply 这里只是将我们的两个参数的函数转换为一个接受数组 包含 这两个参数的函数,以匹配 partition.[=26= 的输出]

由于使用了useWith,这个版本对我来说有点反感;这是一个非常强大且通常简洁的函数,但它通常会损害可读性。 (如果我要将它用于 public 函数,而不是像此处那样匿名使用,那么我可能会向它添加一个 identity [useWith (prepend, [reduce (combine, {}), identity] )],以便返回函数正确地报告了它的元数。但这是一个次要的问题。)

不过,这并不是一个糟糕的实现,而且它的优点是简洁。

const input = [
  {Id: 10, name: "Test_10", points: [1, 2, 3], label: "Label_10", level: "1", unit: "%"},
  {Id: 20, name: "Test_10", points: [4, 5, 6, 7], label: "Label_10", level: "1", unit: "%"},
  {Id: 30, name: "Test_10", points: [8, 9], label: "Label_10", level: "2", unit: "°C"},
  {Id: 40, name: "Test_10", points: [10, 11, 12], label: "Label_10", level: "2", unit: "°C"}
];

const mergeLevelOnePoint = R.pipe(
  R.partition (R.propEq('level', '1')),
  R.zipWith(
    R.call,
    [
      R.pipe(
        R.reduce
          (R.mergeDeepWithKey((key, left, right) => (
            key === 'points'
              ? R.concat(left, right)
              : right
          )))
          ({}),
        R.of,
      ),
      R.identity,
    ],
  ),
  R.unnest,
);

console.log(mergeLevelOnePoint(input));
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>

Ramda 中更易于阅读的无点解决方案:

  1. R.partition 将输入拆分为 [ levelOneEntries, otherLevelEntries ]
  2. 管道分区输出到 R.zipWith(R.call, [fn1, fn2])[fn1(levelOneEntries), fn2(otherLevelEntries)]fn1 深度合并条目,连接 points;而 fn2 只是一个恒等函数。
  3. R.zipWith会输出一个[[mergedLevelOneEntry], otherLevelEntries],这是Array of Array的一种形式。因此,使用 R.unnest 展平为一个数组,即 [mergedLevelOneEntry, otherLevelEntryA, otherLevelEntryB, ...]