如何对具有多个属性的数组进行分组

How to group array with multiple properties

我对 Javascript 中的分组有疑问。

我有一个如下所示的数组,其中每个项目保证只有 is_sweetis_spicyis_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_sweetfood.is_spicyfood.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)。您的累加器可以是任何值,具体取决于您希望输出的内容。在这种情况下,我认为它是一个对象,将您的组名称(isSweetisSpicy 等)映射到数组效果最好。

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: [],
  }
);

然后您可以使用对象展开运算符将组作为变量。