【问题标题】:JavaScript native groupBy reduceJavaScript 原生 groupBy reduce
【发布时间】:2023-03-17 01:01:01
【问题描述】:

我正在使用 JavaScript 原生 reduce,但是我想稍微改变分组以获得我想要的结果。 我有一个数组如下:

const people = [
  {name: "John", age: 23, city: "Seattle", state: "WA"},
  {name: "Mark", age: 25, city: "Houston", state: "TX"},
  {name: "Luke", age: 26, city: "Seattle", state: "WA"},
  {name: "Paul", age: 28, city: "Portland", state: "OR"},
  {name: "Matt", age: 21, city: "Oakland", state: "CA"},
  {name: "Sam", age: 24, city: "Oakland", state: "CA"}
]

我想把它分组并改成这样:

const arranged = [
  {
    city: "Seattle",
    state: "WA",
    persons: [
      { name: "John", age: 23 },
      {name: "Luke", age: 26}
    ]
  },
    {
    city: "Houston",
    state: "TX",
    persons: [
      {name: "Mark", age: 25}
    ]
  },
  {
    city: "Portland",
    state: "OR",
    persons : [
      {name: "Paul", age: 28}
    ]
  },
  {
    city: "Oakland",
    state: "CA",
    persons: [
      {name: "Matt", age: 21},
      {name: "Sam", age: 24}
    ]
  }
]

【问题讨论】:

  • 请同时添加您的代码。

标签: javascript arrays functional-programming reduce


【解决方案1】:

您可以使用Map 和字符串化对象作为分组键。

稍后用键和分组人员的对象渲染想要的数组。

var people = [{ name: "John", age: 23, city: "Seattle", state: "WA" }, { name: "Mark", age: 25, city: "Houston", state: "TX" }, { name: "Luke", age: 26, city: "Seattle", state: "WA" }, { name: "Paul", age: 28, city: "Portland", state: "OR" }, { name: "Matt", age: 21, city: "Oakland", state: "CA" }, { name: "Sam", age: 24, city: "Oakland", state: "CA" }],
    arranged = Array.from(
        people.reduce((m, o) => {
            var key = JSON.stringify(Object.assign(...['city', 'state'].map(k => ({ [k]: o[k] }))));
            return m.set(key, (m.get(key) || []).concat({ name: o.name, age: o.age }));
        }, new Map),
        ([key, persons]) => Object.assign(JSON.parse(key), { persons })
    );

console.log(arranged);
.as-console-wrapper { max-height: 100% !important; top: 0; }

【讨论】:

  • 这很好;我想你会喜欢我的回答如何避免JSON 转换:D
【解决方案2】:

试试这个。我使用 Array.prototype.forEachArray.prototype.push

const people = [
  {name: "John", age: 23, city: "Seattle", state: "WA"},
  {name: "Mark", age: 25, city: "Houston", state: "TX"},
  {name: "Luke", age: 26, city: "Seattle", state: "WA"},
  {name: "Paul", age: 28, city: "Portland", state: "OR"},
  {name: "Matt", age: 21, city: "Oakland", state: "CA"},
  {name: "Sam", age: 24, city: "Oakland", state: "CA"}
];
var arranged=[];
people.forEach(function(e){
    var exist=false;
    arranged.forEach(function(e1){
        if(e1.state===e.state){
            exist=true;
            e1.persons.push({name:e.name,age:e.age});
        }
    });
    if(!exist){
        arranged.push({state:e.state,city:e.city,persons:[{name:e.name,age:e.age}]}); 
     }
});
console.log(arranged);

【讨论】:

    【解决方案3】:

    这不是一个小问题。您首先必须定义 什么 构成一个分组,并且您还必须定义 如何 类似的术语将被组合。您的问题因需要按非原始值进行分组而加剧:城市州。即,我们不能仅根据city 进行分组; more than half the states in the US 有一个城市叫奥克兰。其他答案通过在字符串中序列化城市和州来解决这个问题,但我将向您展示一个更通用的解决方案,适用于任何类型的复合数据。

    这是用函数式编程标记的,所以我将从一个用于分离我们的子任务的模块开始

    const DeepMap =
      { has: (map, [ k, ...ks ]) =>
          ks.length === 0
            ? map.has (k)
            : map.has (k)
              ? DeepMap.has (map.get (k), ks)
              : false
    
      , set: (map, [ k, ...ks ], value) =>
          ks.length === 0
            ? map.set (k, value)
            : map.has (k)
                ? (DeepMap.set (map.get (k), ks, value), map)
                : map.set (k, DeepMap.set (new Map, ks, value))
    
      , get: (map, [ k, ...ks ]) =>
          ks.length === 0
            ? map.get (k)
            : map.has (k)
              ? DeepMap.get (map.get (k), ks)
              : undefined
      }
    

    现在我们可以定义我们的通用 groupBy 函数

    const identity = x =>
      x
    
    const { has, set, get } =
      DeepMap
    
    const groupBy = (key = identity, value = identity, xs = []) =>
      xs.reduce 
        ((m, x) =>
          has (m, key (x))
            ? set ( m
                  , key (x)
                  , [ ...get (m, key (x)), value (x) ]
                  )
            : set ( m
                  , key (x)
                  , [ value (x) ]
                  )
        , new Map
        )
    

    我们通过指定keyvalue 函数来使用groupBy - key 函数指定项目的分组依据,value 函数指定要添加到组中的值

    const people =
      [ { name: "John", age: 23, city: "Seattle", state: "WA" }
      , { name: "Mark", age: 25, city: "Houston", state: "TX" }
      , { name: "Luke", age: 26, city: "Seattle", state: "WA" }
      , { name: "Paul", age: 28, city: "Portland", state: "OR" }
      , { name: "Matt", age: 21, city: "Oakland", state: "CA" }
      , { name: "Sam", age: 24, city: "Oakland", state: "CA" }
      ]
    
    const res =
      groupBy ( k => [ k.state, k.city ]
              , v => ({ name: v.name, age: v.age })
              , people
              )
    
    console.log (res.get ('WA'))
    // Map { 'Seattle' => [ { name: 'John', age: 23 }, { name: 'Luke', age: 26 } ] }
    
    console.log (res.get ('WA') .get ('Seattle'))
    // [ { name: 'John', age: 23 }, { name: 'Luke', age: 26 } ]
    

    我们可以看到这个中间结果是如何有用的。感谢Map,它提供了令人难以置信的高效查找。当然,您会希望以更有意义的方式遍历深度地图。让我们在我们的模块中添加一个entries 过程

    const DeepMap =
      { ...
      , entries: function* (map, fields = [])
          {
            const loop = function* (m, path, [ f, ...fields ])
            {
              if (fields.length === 0)
                for (const [ key, value ] of m)
                  yield [ { ...path, [ f ]: key }, value ]
              else
                for (const [ key, value ] of m)
                  yield* loop (value, { ...path, [ f ]: key }, fields)
    
            }
            yield* loop (map, {}, fields)
          }
      }
    
    
    for (const [ key, value ] of DeepMap.entries (res, [ 'state', 'city' ]))
      console.log (key, value)
    
    // { state: 'WA', city: 'Seattle' } [ { name: 'John', age: 23 }, { name: 'Luke', age: 26 } ]
    // { state: 'TX', city: 'Houston' } [ { name: 'Mark', age: 25 } ]
    // { state: 'OR', city: 'Portland' } [ { name: 'Paul', age: 28 } ]
    // { state: 'CA', city: 'Oakland' } [ { name: 'Matt', age: 21 }, { name: 'Sam', age: 24 } ]
    

    现在我们的深度图是可迭代的,我们可以使用Array.from轻松生成您想要的输出

    const arranged = 
      Array.from ( entries (res, [ 'state', 'city' ])
                 , ([ key, persons ]) => ({ ...key, persons })
                 )
    
    console.log (arranged)
    // [
    //   {
    //     city: "Seattle",
    //     state: "WA",
    //     persons: [
    //       { name: "John", age: 23 },
    //       { name: "Luke", age: 26 }
    //     ]
    //   },
    //   {
    //     city: "Houston",
    //     state: "TX",
    //     persons: [
    //       { name: "Mark", age: 25 }
    //     ]
    //   },
    //   {
    //     city: "Portland",
    //     state: "OR",
    //     persons : [
    //       { name: "Paul", age: 28 }
    //     ]
    //   },
    //   {
    //     city: "Oakland",
    //     state: "CA",
    //     persons: [
    //       { name: "Matt", age: 21 },
    //       { name: "Sam", age: 24 }
    //     ]
    //   }
    // ]
    

    程序演示

    const DeepMap =
      { has: (map, [ k, ...ks ]) =>
          ks.length === 0
            ? map.has (k)
            : map.has (k)
              ? DeepMap.has (map.get (k), ks)
              : false
    
      , set: (map, [ k, ...ks ], value) =>
          ks.length === 0
            ? map.set (k, value)
            : map.has (k)
                ? (DeepMap.set (map.get (k), ks, value), map)
                : map.set (k, DeepMap.set (new Map, ks, value))
            
      , get: (map, [ k, ...ks ]) =>
          ks.length === 0
            ? map.get (k)
            : map.has (k)
              ? DeepMap.get (map.get (k), ks)
              : undefined
              
      , entries: function* (map, fields = [])
          {
            const loop = function* (m, path, [ f, ...fields ])
            {
              if (fields.length === 0)
                for (const [ key, value ] of m)
                  yield [ { ...path, [ f ]: key }, value ]
              else
                for (const [ key, value ] of m)
                  yield* loop (value, { ...path, [ f ]: key }, fields)
            }
            yield* loop (map, {}, fields)
          }
      }
    
    const identity = x =>
      x
      
    const { has, set, get, entries } =
      DeepMap
    
    const groupBy = (key = identity, value = identity, xs = []) =>
      xs.reduce 
        ((m, x) =>
          has (m, key (x))
            ? set ( m
                  , key (x)
                  , [ ...get (m, key (x)), value (x) ]
                  )
            : set ( m
                  , key (x)
                  , [ value (x) ]
                  )
        , new Map
        )
        
    const people =
      [ { name: "John", age: 23, city: "Seattle", state: "WA" }
      , { name: "Mark", age: 25, city: "Houston", state: "TX" }
      , { name: "Luke", age: 26, city: "Seattle", state: "WA" }
      , { name: "Paul", age: 28, city: "Portland", state: "OR" }
      , { name: "Matt", age: 21, city: "Oakland", state: "CA" }
      , { name: "Sam", age: 24, city: "Oakland", state: "CA" }
      ]
      
    const res =
      groupBy ( k => [ k.state, k.city ]
              , v => ({ name: v.name, age: v.age })
              , people
              )
              
    for (const [ key, value ] of entries (res, [ 'state', 'city' ]))
      console.log (key, value)
    
    // { state: 'WA', city: 'Seattle' } [ { name: 'John', age: 23 }, { name: 'Luke', age: 26 } ]
    // { state: 'TX', city: 'Houston' } [ { name: 'Mark', age: 25 } ]
    // { state: 'OR', city: 'Portland' } [ { name: 'Paul', age: 28 } ]
    // { state: 'CA', city: 'Oakland' } [ { name: 'Matt', age: 21 }, { name: 'Sam', age: 24 } ]
      
    const arranged = 
      Array.from ( entries (res, [ 'state', 'city '])
                 , ([ key, persons ]) => ({ ...key, persons })
                 )
                 
    console.log ('arranged', arranged)
    // arranged [
    //   {
    //     city: "Seattle",
    //     state: "WA",
    //     persons: [
    //       { name: "John", age: 23 },
    //       { name: "Luke", age: 26 }
    //     ]
    //   },
    //   {
    //     city: "Houston",
    //     state: "TX",
    //     persons: [
    //       { name: "Mark", age: 25 }
    //     ]
    //   },
    //   {
    //     city: "Portland",
    //     state: "OR",
    //     persons : [
    //       { name: "Paul", age: 28 }
    //     ]
    //   },
    //   {
    //     city: "Oakland",
    //     state: "CA",
    //     persons: [
    //       { name: "Matt", age: 21 },
    //       { name: "Sam", age: 24 }
    //     ]
    //   }
    // ]

    【讨论】:

    • 我希望我有更多时间专门回答这个问题。如果您对此感兴趣,我会以其他方式解决此类似问题 herehere
    【解决方案4】:

    您可以使用函数reduce 来分组和构建所需的输出。

    const people = [  {name: "John", age: 23, city: "Seattle", state: "WA"},  {name: "Mark", age: 25, city: "Houston", state: "TX"},  {name: "Luke", age: 26, city: "Seattle", state: "WA"},  {name: "Paul", age: 28, city: "Portland", state: "OR"},  {name: "Matt", age: 21, city: "Oakland", state: "CA"},  {name: "Sam", age: 24, city: "Oakland", state: "CA"}]
    
    const result = Object.values(people.reduce((a, {name, age, city, state}) => {
      var key = [city, state].join('|');
      (a[key] || (a[key] = {city, state, persons: []})).persons.push({name, age});
      return a;
    }, {}));
    
    console.log(result);
    .as-console-wrapper { max-height: 100% !important; top: 0; }

    【讨论】:

    • 美国一半以上的州都有一个名为(例如)Oakland 的城市 - 您的条件应该匹配城市州!
    • @naomik 有趣!
    • 有没有办法可以完全抽象出这个方法。我已经尝试过了,但是没有用.. function structuringData(rawData, allFields, matchingKey, parentObject, groupedArray, childObject ){ const result = Object.values(rawData.reduce((a, {allFields}) => { (a[matchingCondition] || (a[matchingCondition] = {parentObject, groupedArray: []}))[groupedArray].push({groupedObject}); return a; }, {}));返回结果;
    【解决方案5】:

    我已经通过 reducer 构建了一个通用组,您将要分组的键传递给它,它会为您提供自定义的 reducer 功能。此 reducer 为您提供了一个由(组合或简单)键索引的对象,该键包含共享此键的项目数组。您可以重复使用它以按您想要的键对其进行分组。

    这里有两个例子。

    const people = Object.freeze([{
      name: "John",
      age: 23,
      city: "Seattle",
      state: "WA"
    }, {
      name: "Mark",
      age: 25,
      city: "Houston",
      state: "TX"
    }, {
      name: "Luke",
      age: 26,
      city: "Seattle",
      state: "WA"
    }, {
      name: "Paul",
      age: 28,
      city: "Portland",
      state: "OR"
    }, {
      name: "Matt",
      age: 21,
      city: "Oakland",
      state: "CA"
    }, {
      name: "Sam",
      age: 24,
      city: "Oakland",
      state: "CA"
    }]);
    
    const groupByReducer = (group) =>
      (result, row) => {
    
        const keygroup = group.map((v) => row[v]);
        const key = keygroup.join(':');
    
        if (result[key])
          result[key].push(row);
        else
          result[key] = [row];
    
        return result;
    
      };
    
    const byCityState = people.reduce(
      groupByReducer(['city', 'state']), {});
    const byState = people.reduce(groupByReducer(['state']), {});
    
    console.log(byCityState);
    console.log(byState);
    .as-console-wrapper {
      max-height: 100% !important;
      top: 0;
    }

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-24
      • 1970-01-01
      • 2020-02-05
      • 2012-04-30
      • 1970-01-01
      相关资源
      最近更新 更多