JavaScript native groupBy reduce

后端 未结 5 512
甜味超标
甜味超标 2021-01-23 08:30

I am using JavaScript native reduce, however I want to slightly change in the grouping to get my desired result. I have an array as follows:

const people = [
          


        
相关标签:
5条回答
  • 2021-01-23 09:09

    Try this. I use Array.prototype.forEach and Array.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);

    0 讨论(0)
  • 2021-01-23 09:12

    You can use the function reduce to group and build the desired output.

    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; }

    0 讨论(0)
  • 2021-01-23 09:16

    I have built a generic group by reducer, you pass it the keys by which you want to group and it gives you a custom reducer function. This reducer gives you an object indexed by a (composed or simple) key containing an array of items that share this key. You can reuse it to have it grouped by the key(s) you want to.

    Here are two examples.

    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;
    }

    0 讨论(0)
  • 2021-01-23 09:25

    You could use a Map and a stringified object as key for grouping.

    Later render the wanted array with objects of the keys and the grouped persons.

    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; }

    0 讨论(0)
  • 2021-01-23 09:26

    This is not a trivial problem. You first have to define what constitutes a grouping, and you also have to define how like terms will be combined. You problem is exacerbated by the need to group by a non-primitive value: city and state. Ie, we can't just group based on city alone; more than half the states in the US have a city named Oakland. Other answers solve this by serializing the city and state in a string, but I will show you a more generic solution that works for compound data of any type.

    This is tagged with functional programming, so I'll start with a module for separating our subtasks

    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
      }
    

    Now we can define our generic groupBy function

    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
        )
    

    We use groupBy by specifying a key and value functions – The key function specifies what an item is grouped by, and the value functions specifies the value to be added to the group

    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 } ]
    

    We can see how this intermediate result would be useful. It provides incredibly efficient lookup thanks to Map. Of course you'll want to iterate thru the deep map in more meaningful ways though. Let's add an entries procedure to our module

    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 } ]
    

    Now that our deep map is iterable, we can easily produce your desired output using 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 }
    //     ]
    //   }
    // ]
    

    Program demonstration

    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 }
    //     ]
    //   }
    // ]

    0 讨论(0)
提交回复
热议问题