如何对具有多个属性的数组进行分组
How to group array with multiple properties
我对 Javascript 中的分组有疑问。
我有一个如下所示的数组,其中每个项目保证只有 is_sweet
、is_spicy
或 is_bitter
中的一个等于 true
:
const foodsData = [{
name: 'mie aceh',
is_sweet: false,
is_spicy: true,
is_bitter: false,
}, {
name: 'nasi padang',
is_sweet: false,
is_spicy: true,
is_bitter: false,
}, {
name: 'serabi',
is_sweet: true,
is_spicy: false,
is_bitter: false,
}, {
name: 'onde-onde',
is_sweet: true,
is_spicy: false,
is_bitter: false,
}, {
name: 'pare',
is_sweet: false,
is_spicy: false,
is_bitter: true,
}];
我想要的输出是根据第一个元素 is_sweet、第二个元素 is_bitter 和 third_element is_spicy 对数组进行分组像下面的输出:
[
[
{ name: 'serabi',
is_sweet: true,
is_spicy: false,
is_bitter: false },
{ name: 'onde-onde',
is_sweet: true,
is_spicy: false,
is_bitter: false }
],
[
{ name: 'pare',
is_sweet: false,
is_spicy: false,
is_bitter: true }
],
[
{ name: 'mie aceh',
is_sweet: false,
is_spicy: true,
is_bitter: false },
{ name: 'nasi padang',
is_sweet: false,
is_spicy: true,
is_bitter: false }
]
]
我尝试通过编写以下代码来做到这一点
const isSweet = foodsData.filter(food => food.is_sweet);
const isBitter = foodsData.filter(food => food.is_bitter);
const isSpicy = foodsData.filter(food => food.is_spicy);
const newFood = [];
newFood.push(isSweet);
newFood.push(isBitter);
newFood.push(isSpicy);
有没有一种方法可以得到相同的结果,但使用 vanilla Javascript 或 Lodash 的代码更清晰?
您已阐明每个项目只能具有三个属性之一 is_sweet
/is_spicy
/is_bitter
等于 true
.
鉴于此假设,一种方法是我们可以根据商品 x
的数量值 x.is_sweet*4 + x.is_spicy*2 + x.is_bitter*1
对商品进行分组。对于那些只有 is_bitter: true
的人,这个数量等于 1,对于只有 is_spicy: true
的项目,这个数量等于 2,对于只有 is_sweet: true
.
的项目,这个数量等于 4
根据该数量分组后,您可以使用 _.values
删除由 _.groupBy
创建的密钥:
_.values(_.groupBy(foodsData, x => x.is_sweet*4 + x.is_spicy*2 + x.is_bitter*1))
显然你可以给谓词起一个描述性的名字:
const flavour = food => food.is_sweet*4 + food.is_spicy*2 + food.is_bitter*1;
let groupedFoods = _.values(_.groupBy(foodsData, flavour))
请注意,如果允许项的三个键中有一个以上为真,则上面的代码仍会给出合理的结果,也就是说,相对于 [is_sweet, is_spicy, is_bitter]
的八组相等到 [0,0,0]
、[0,0,1]
、[0,1,0]
、[0,1,1]
、[1,0,0]
、[1,0,1]
、[1,1,0]
和 [1,1,1]
。
事后看来,我们不需要谓词 flavour
为每个条目拉出一个数字 food
;根据上一段,如果它提取三个值 food.is_sweet
、food.is_spicy
和 food.is_bitter
并将它们放入数组中,我们会很高兴。 Lodash 对此有 _.over
,所以我们可以定义
const flavour = _.over([is_sweet, is_spicy, is_bitter]);
关于上述解决方案的可读性的几句话。
_.groupBy
是比 reduce
类(成员或自由)函数更高层次的抽象;换句话说,后者的功能比问题中用例所需的功能强大得多,因此它们的表现力不如前者。事实上,_.groupBy
完全可以根据 reduce
来实现。这是一个残酷的实现来证明这是可能的:
const groupBy = (things, f) => {
return things.reduce((acc, item) => {
if (acc[f(item)] == undefined)
acc[f(item)] = [item];
else
acc[f(item)].push(item);
return acc;
}, {});
}
// both these give the same result:
groupBy([1,2,3,4,4,4], x => x % 2 == 0); // mine
_.groupBy([1,2,3,4,4,4], x => x % 2 == 0); // lodash's
解决方案在文字上非常简短:它看起来多么“复杂”或“花哨”并不重要;一旦你习惯了这种做事方式(又名正确的方式),你最终会阅读 代码。再看一遍:
_.values(_.groupBy(foodsData, flavour))
这已经相当可读了,但是如果你使用 lodash/fp
模块,你甚至可以改进它:
_.values(_.groupBy(flavour, foodsData))
这离下面的英文句子有多远?
"get the values of the result of grouping by flavor the foods"
说不可读就是否认事物的真实性。
更长的解决方案使用较低的杠杆结构,如 for
循环或(更好一点)reduce
-like 实用程序迫使你 相反,解析 代码。如果不仔细 检查 他们的身体,您就无法理解这些解决方案的作用。即使您决定将函数 (acc, item) => { … }
从对 reduce
的调用中拉出来并给它起一个名字,以 input.reduce(<i>fun</i>, <i>acc</i>)
你会怎么命名呢?它是一个将元素推到数组顶部的中间结果的函数。除非在琐碎的用例中,否则您不会为这样满嘴的东西找到一个好名字。您只是将问题转移到其他地方。看了这段代码的人还是会一头雾水。
_.over
可能也有点吓人:
const flavour = _.over([is_sweet, is_spicy, is_bitter]);
但实际上,您只是学习并接受 _.over
的含义需要付出什么代价?我的意思是,这就像学习一门新语言,仅此而已。 And the explanation of _.over
is fairly simple:
_.over([iteratees=[_.identity]])
Creates a function that invokes iteratees
with the arguments it receives and returns their results.
就是这么简单:_.over(['a', 'b', 'd'])({a: 1, b: 2, c: 3}) == [1, 2, undefined]
虽然其他两个答案是正确的,但我个人认为这个最易读。
对于您的示例输入:
const input = [
{
name: "mie aceh",
is_sweet: false,
is_spicy: true,
is_bitter: false,
},
...
];
您可以对数组调用reduce。减少数组,迭代数组中的项目,减少这些值到累加器(acc
)。您的累加器可以是任何值,具体取决于您希望输出的内容。在这种情况下,我认为它是一个对象,将您的组名称(isSweet
、isSpicy
等)映射到数组效果最好。
const { isSweet, isSpicy, isBitter } = input.reduce(
(acc, item) => {
if (item.is_sweet) {
acc.isSweet.push(item);
} else if (item.is_spicy) {
acc.isSpicy.push(item);
} else if (item.is_bitter) {
acc.isBitter.push(item);
}
return acc;
},
{
isSweet: [],
isSpicy: [],
isBitter: [],
}
);
然后您可以使用对象展开运算符将组作为变量。
我对 Javascript 中的分组有疑问。
我有一个如下所示的数组,其中每个项目保证只有 is_sweet
、is_spicy
或 is_bitter
中的一个等于 true
:
const foodsData = [{
name: 'mie aceh',
is_sweet: false,
is_spicy: true,
is_bitter: false,
}, {
name: 'nasi padang',
is_sweet: false,
is_spicy: true,
is_bitter: false,
}, {
name: 'serabi',
is_sweet: true,
is_spicy: false,
is_bitter: false,
}, {
name: 'onde-onde',
is_sweet: true,
is_spicy: false,
is_bitter: false,
}, {
name: 'pare',
is_sweet: false,
is_spicy: false,
is_bitter: true,
}];
我想要的输出是根据第一个元素 is_sweet、第二个元素 is_bitter 和 third_element is_spicy 对数组进行分组像下面的输出:
[
[
{ name: 'serabi',
is_sweet: true,
is_spicy: false,
is_bitter: false },
{ name: 'onde-onde',
is_sweet: true,
is_spicy: false,
is_bitter: false }
],
[
{ name: 'pare',
is_sweet: false,
is_spicy: false,
is_bitter: true }
],
[
{ name: 'mie aceh',
is_sweet: false,
is_spicy: true,
is_bitter: false },
{ name: 'nasi padang',
is_sweet: false,
is_spicy: true,
is_bitter: false }
]
]
我尝试通过编写以下代码来做到这一点
const isSweet = foodsData.filter(food => food.is_sweet);
const isBitter = foodsData.filter(food => food.is_bitter);
const isSpicy = foodsData.filter(food => food.is_spicy);
const newFood = [];
newFood.push(isSweet);
newFood.push(isBitter);
newFood.push(isSpicy);
有没有一种方法可以得到相同的结果,但使用 vanilla Javascript 或 Lodash 的代码更清晰?
您已阐明每个项目只能具有三个属性之一 is_sweet
/is_spicy
/is_bitter
等于 true
.
鉴于此假设,一种方法是我们可以根据商品 x
的数量值 x.is_sweet*4 + x.is_spicy*2 + x.is_bitter*1
对商品进行分组。对于那些只有 is_bitter: true
的人,这个数量等于 1,对于只有 is_spicy: true
的项目,这个数量等于 2,对于只有 is_sweet: true
.
根据该数量分组后,您可以使用 _.values
删除由 _.groupBy
创建的密钥:
_.values(_.groupBy(foodsData, x => x.is_sweet*4 + x.is_spicy*2 + x.is_bitter*1))
显然你可以给谓词起一个描述性的名字:
const flavour = food => food.is_sweet*4 + food.is_spicy*2 + food.is_bitter*1;
let groupedFoods = _.values(_.groupBy(foodsData, flavour))
请注意,如果允许项的三个键中有一个以上为真,则上面的代码仍会给出合理的结果,也就是说,相对于 [is_sweet, is_spicy, is_bitter]
的八组相等到 [0,0,0]
、[0,0,1]
、[0,1,0]
、[0,1,1]
、[1,0,0]
、[1,0,1]
、[1,1,0]
和 [1,1,1]
。
事后看来,我们不需要谓词 flavour
为每个条目拉出一个数字 food
;根据上一段,如果它提取三个值 food.is_sweet
、food.is_spicy
和 food.is_bitter
并将它们放入数组中,我们会很高兴。 Lodash 对此有 _.over
,所以我们可以定义
const flavour = _.over([is_sweet, is_spicy, is_bitter]);
关于上述解决方案的可读性的几句话。
_.groupBy
是比reduce
类(成员或自由)函数更高层次的抽象;换句话说,后者的功能比问题中用例所需的功能强大得多,因此它们的表现力不如前者。事实上,_.groupBy
完全可以根据reduce
来实现。这是一个残酷的实现来证明这是可能的:const groupBy = (things, f) => { return things.reduce((acc, item) => { if (acc[f(item)] == undefined) acc[f(item)] = [item]; else acc[f(item)].push(item); return acc; }, {}); } // both these give the same result: groupBy([1,2,3,4,4,4], x => x % 2 == 0); // mine _.groupBy([1,2,3,4,4,4], x => x % 2 == 0); // lodash's
解决方案在文字上非常简短:它看起来多么“复杂”或“花哨”并不重要;一旦你习惯了这种做事方式(又名正确的方式),你最终会阅读 代码。再看一遍:
_.values(_.groupBy(foodsData, flavour))
这已经相当可读了,但是如果你使用
lodash/fp
模块,你甚至可以改进它:_.values(_.groupBy(flavour, foodsData))
这离下面的英文句子有多远?
"get the values of the result of grouping by flavor the foods"
说不可读就是否认事物的真实性。
更长的解决方案使用较低的杠杆结构,如
for
循环或(更好一点)reduce
-like 实用程序迫使你 相反,解析 代码。如果不仔细 检查 他们的身体,您就无法理解这些解决方案的作用。即使您决定将函数(acc, item) => { … }
从对reduce
的调用中拉出来并给它起一个名字,以input.reduce(<i>fun</i>, <i>acc</i>)
你会怎么命名呢?它是一个将元素推到数组顶部的中间结果的函数。除非在琐碎的用例中,否则您不会为这样满嘴的东西找到一个好名字。您只是将问题转移到其他地方。看了这段代码的人还是会一头雾水。_.over
可能也有点吓人:const flavour = _.over([is_sweet, is_spicy, is_bitter]);
但实际上,您只是学习并接受
_.over
的含义需要付出什么代价?我的意思是,这就像学习一门新语言,仅此而已。 And the explanation of_.over
is fairly simple:_.over([iteratees=[_.identity]])
Creates a function that invokes
iteratees
with the arguments it receives and returns their results.就是这么简单:
_.over(['a', 'b', 'd'])({a: 1, b: 2, c: 3}) == [1, 2, undefined]
虽然其他两个答案是正确的,但我个人认为这个最易读。
对于您的示例输入:
const input = [
{
name: "mie aceh",
is_sweet: false,
is_spicy: true,
is_bitter: false,
},
...
];
您可以对数组调用reduce。减少数组,迭代数组中的项目,减少这些值到累加器(acc
)。您的累加器可以是任何值,具体取决于您希望输出的内容。在这种情况下,我认为它是一个对象,将您的组名称(isSweet
、isSpicy
等)映射到数组效果最好。
const { isSweet, isSpicy, isBitter } = input.reduce(
(acc, item) => {
if (item.is_sweet) {
acc.isSweet.push(item);
} else if (item.is_spicy) {
acc.isSpicy.push(item);
} else if (item.is_bitter) {
acc.isBitter.push(item);
}
return acc;
},
{
isSweet: [],
isSpicy: [],
isBitter: [],
}
);
然后您可以使用对象展开运算符将组作为变量。