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 中更易于阅读的无点解决方案:
R.partition
将输入拆分为 [ levelOneEntries, otherLevelEntries ]
- 管道分区输出到
R.zipWith(R.call, [fn1, fn2])
,[fn1(levelOneEntries), fn2(otherLevelEntries)]
。 fn1
深度合并条目,连接 points
;而 fn2
只是一个恒等函数。
R.zipWith
会输出一个[[mergedLevelOneEntry], otherLevelEntries]
,这是Array of Array的一种形式。因此,使用 R.unnest
展平为一个数组,即 [mergedLevelOneEntry, otherLevelEntryA, otherLevelEntryB, ...]
我有以下数据结构作为输入
我想在不丢失任何数据的情况下对数据进行特殊分组
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 中更易于阅读的无点解决方案:
R.partition
将输入拆分为[ levelOneEntries, otherLevelEntries ]
- 管道分区输出到
R.zipWith(R.call, [fn1, fn2])
,[fn1(levelOneEntries), fn2(otherLevelEntries)]
。fn1
深度合并条目,连接points
;而fn2
只是一个恒等函数。 R.zipWith
会输出一个[[mergedLevelOneEntry], otherLevelEntries]
,这是Array of Array的一种形式。因此,使用R.unnest
展平为一个数组,即[mergedLevelOneEntry, otherLevelEntryA, otherLevelEntryB, ...]