Reducing/Grouping an array in Javascript

前端 未结 2 1722
误落风尘
误落风尘 2021-01-29 15:06

Based on this example, I want to group by object in a slightly other way. The outcome should be as follows:

[{
  key: \"audi\"
  items: [
    {
      \"make\": \         


        
相关标签:
2条回答
  • 2021-01-29 15:48

    @Nina's answer is practical, efficient, and definitely the answer you should be reading. However, these problems are interesting to me and I like thinking about solving them in other ways, even if that means making trades.

    Compound data equality in JavaScript

    Testing for compound data equality in JavaScript can be a bother

    console.log (1 === 1)         // true
    console.log ('a' === 'a')     // true
    console.log ([1,2] === [1,2]) // false
    console.log ({a:1} === {a:1}) // false

    This simple-natured equality test can make it somewhat challenging to deal with JavaScript's native Set and Map too

    const m = new Map ()
    m.set ([1,2], 'hello')
    console.log (m.get ([1,2]))                      // undefined
    console.log (m.get (Array.from (m.keys ()) [0])) // 'hello'

    Bugger! Compound data equality bit us again. m.get cannot find the key [1,2] because the first key (that we set) [1,2] is different from the second key (to get) [1,2] – ie, the two instances of [1,2] are in different memory locations and are therefore considered (by JavaScript) to be inequal (!==)


    Compound data equality, take 2

    We don't have to play by JavaScript's rules, if we don't want to. In this part of the answer, we make our own Dict (dictionary) compound data type that accepts a function that is used to determine key equality

    Imagine Dict working something like this

    const d = Dict (({a} => a)
    d.has ({a:1}) // false
    d.set ({a:1}, 'hello') .has ({a:1}) // true
    d.set ({a:1}, 'hello') .get ({a:1}) // 'hello'
    d.get ({a:2}) // undefined
    d.set ({a:2}, 'world') .get ({a:2}) // 'world'
    

    If we had a data type that worked like Dict, then we could easily write the necessary transformation for our data

    // our Dict type with custom key comparator
    const DictByMake =
      Dict (x => x.make)
    
    const dict =
      data.reduce((d, item) =>
        d.set (item, d.has (item)
          ? d.get (item) .concat ([item])
          : [item]), DictByMake ())
    

    I say if we had a data type like Dict because it's good to be optimistic. Why should I make sacrifices and pick a data type incapable of fulfilling my needs when I don't have to? If a type I need doesn't exist, I can just make one. Thanks in advance, JavaScript !

    Below I implement Dict with some consistency to JS's Set and Map – the most notable difference here is Dict is persistent (immutable) (a matter of preference, in this case)

    const Pair = (left, right) => ({
      left,
      right
    })
    
    const Dict = eq => (pairs=[]) => ({
      equals (x, y) {
        return eq (x) === eq (y)
      },
      has (k) {
        for (const {left} of pairs)
          if (this.equals (k, left))
            return true
        return false
      },
      get (k) {
        for (const {left, right} of pairs)
          if (this.equals (k, left))
            return right
        return undefined
      },
      set (k, v) {
        for (const [i, {left, right}] of pairs.entries ())
          if (this.equals (k, left))
            return Dict (eq) (pairs
              .slice (0, i)
              .concat ([Pair (k, v)])
              .concat (pairs.slice (i+1)))
        return Dict (eq) (pairs.concat ([Pair (k, v)]))
      },
      entries () {
        return {
          *[Symbol.iterator] () {
            for (const {left, right} of pairs)
              yield [eq (left), right]
          }
        }
      }
    })
    
    const DictByMake =
      Dict (x => x.make)
    
    const main = data => {
      // build the dict
      const dict =
        data.reduce((d, x) =>
          d.set(x, d.has (x)
            ? [...d.get (x), x]
            : [x]), DictByMake ())
      // convert dict key/value pairs to desired {key, items} shape
      return Array.from (dict.entries (), ([key, items]) =>
          ({ key, items }))
    } 
    
    const data = 
      [{ make: 'audi', model: 'r8', year: '2012' }, { make: 'audi', model: 'rs5', year: '2013' }, { make: 'ford', model: 'mustang', year: '2012' }, { make: 'ford', model: 'fusion', year: '2015' }, { make: 'kia', model: 'optima', year: '2012' }]
      
    console.log (main (data))


    Compound data equality, take 3

    OK, that was a little intense inventing our very own data type ! In the example above, we based Dict off of an Array (native) of pairs – this naïve implementation detail makes Dict inefficient compared to other associative types that instead use a binary search tree or hash table to get/set keys. I used this an example to show how to build a more complex type from a more primitive one, but we could've just as easily made our own Tree type and used that instead.

    In reality, we are given Map by JavaScript and don't have (get) to worry about how it's implemented – and while it doesn't have the exact behavior we want, we can adapt its behavior slightly without having to invent an entirely new type from scratch.

    Worth noting, MapBy is not implemented as a persistent structure here

    const MapBy = ord => (map = new Map ()) => ({
      has: k =>
        map.has (ord (k)),
      get: k =>
        map.get (ord (k)),
      set: (k, v) =>
        MapBy (ord) (map.set (ord (k), v)),
      keys: () =>
        map.keys (),
      values: () =>
        map.values (),
      entries: () =>
        map.entries ()
    })
    
    // the rest of the program stays exactly the same (with exception to variable names)
    const MapByMake =
      MapBy (x => x.make)
    
    const main = data => {
      const map =
        data.reduce((m, x) =>
          m.set(x, m.has (x)
            ? [...m.get (x), x]
            : [x]), MapByMake ())
      return Array.from (map.entries (), ([key, items]) =>
          ({ key, items }))
    } 
    
    const data = 
      [{ make: 'audi', model: 'r8', year: '2012' }, { make: 'audi', model: 'rs5', year: '2013' }, { make: 'ford', model: 'mustang', year: '2012' }, { make: 'ford', model: 'fusion', year: '2015' }, { make: 'kia', model: 'optima', year: '2012' }]
          
    console.log (main (data))

    0 讨论(0)
  • 2021-01-29 15:58

    You could use a hash table for grouping by make and an array for the wanted result.

    For every group in hash, a new object, like

    {
        key: a.make,
        items: []
    }
    

    is created and pushed to the result set.

    The hash table is initialized with a really empty object. There are no prototypes, to prevent collision.

    var cars = [{ make: 'audi', model: 'r8', year: '2012' }, { make: 'audi', model: 'rs5', year: '2013' }, { make: 'ford', model: 'mustang', year: '2012' }, { make: 'ford', model: 'fusion', year: '2015' }, { make: 'kia', model: 'optima', year: '2012' }],
        hash = Object.create(null),
        result = [];
    
    cars.forEach(function (a) {
        if (!hash[a.make]) {
            hash[a.make] = { key: a.make, items: [] };
            result.push(hash[a.make]);
        }
        hash[a.make].items.push(a);
    });
    
    console.log(result);
    .as-console-wrapper { max-height: 100% !important; top: 0; }

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