Bir nesne dizisini tuşla gruplandırma


154

Herkes bir nesne anahtarıyla bir nesne dizisini gruplamak (sonra da mümkünse lodash) bir yol biliyor ve sonra gruplandırmaya dayalı yeni bir nesne dizisi oluşturmak? Örneğin, araba nesneleri bir dizi var:

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'
    },
];

Tarafından gruplandırılmış araba nesneleri yeni bir dizi yapmak istiyorum make:

var cars = {
    'audi': [
        {
            'model': 'r8',
            'year': '2012'
        }, {
            'model': 'rs5',
            'year': '2013'
        },
    ],

    'ford': [
        {
            'model': 'mustang',
            'year': '2012'
        }, {
            'model': 'fusion',
            'year': '2015'
        }
    ],

    'kia': [
        {
            'model': 'optima',
            'year': '2012'
        }
    ]
}

1
Baktın mı groupBy?
SLaks

2
sonucunuz geçerli değil.
Nina Scholz

Nesne yerine Harita almak için benzer bir yaklaşım var mı?
Andrea Bergonzo

Yanıtlar:


104

Timo'nun cevabı nasıl yapacağım. Basit _.groupByve gruplandırılmış yapıdaki nesnelerde bazı kopyalara izin verir.

Ancak OP, yinelenen makeanahtarların kaldırılmasını da istedi . Eğer sonuna kadar gitmek istersen:

var grouped = _.mapValues(_.groupBy(cars, 'make'),
                          clist => clist.map(car => _.omit(car, 'make')));

console.log(grouped);

Verim:

{ audi:
   [ { model: 'r8', year: '2012' },
     { model: 'rs5', year: '2013' } ],
  ford:
   [ { model: 'mustang', year: '2012' },
     { model: 'fusion', year: '2015' } ],
  kia: [ { model: 'optima', year: '2012' } ] }

Bunu Underscore.js kullanarak yapmak istiyorsanız, sürümünün _.mapValuesçağrıldığına dikkat edin _.mapObject.


278

Düz Javascript'te, Array#reducebir nesneyle kullanabilirsiniz

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' }],
    result = cars.reduce(function (r, a) {
        r[a.make] = r[a.make] || [];
        r[a.make].push(a);
        return r;
    }, Object.create(null));

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


1
resultsonuçları nasıl tekrarlayabilirim ?
Mounir Elfassi

1
girişleri alabilir ve Object.entriesanahtar / değer çiftleri arasında geçiş yapabilirsiniz.
Nina Scholz

makeGruplandıktan sonra veri kümesinden kaldırmanın bir yolu var mı ? Fazladan yer kaplıyor.
Mercurial


R ve ne anlama geliyor? R'nin akümülatör ve bir currentValue olduğunu varsaymak doğru olur mu?
Omar

68

Arıyorsun _.groupBy().

Gruplandırdığınız özelliği nesnelerden kaldırmak gerektiğinde önemsiz olmalıdır:

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'},];

var grouped = _.groupBy(cars, function(car) {
  return car.make;
});

console.log(grouped);
<script src='https://cdn.jsdelivr.net/lodash/4.17.2/lodash.min.js'></script>


Bonus olarak, ES6 ok işlevleri ile daha da iyi bir sözdizimi elde edersiniz:

const grouped = _.groupBy(cars, car => car.make);

18
Ve yine daha kısa istiyorsanız, var grouped = _.groupBy(cars, 'make');herhangi bir işleve gerek yok, eğer erişimci basit bir özellik adı ise.
Jonathan Eunice

1
'_' Ne anlama geliyor?
Adrian Grzywaczewski

@AdrianGrzywaczewski, ad aralığı 'lodash' veya 'alt çizgi' için varsayılan kuraldı. Şimdi kütüphaneler modüler olduğu için artık gerekli değildir. npmjs.com/package/lodash.groupby
vilsbole

5
Ve sonuca nasıl katılabilirim?
Luis Antonio Pestana

36

Bir nesne dizisini es6'da belirli bir tuşla gruplamak için kullanılan kısa sürüm:

result = array.reduce((h, obj) => Object.assign(h, { [obj.key]:( h[obj.key] || [] ).concat(obj) }), {})

Daha uzun versiyon:

result = array.reduce(function(h, obj) {
  h[obj.key] = (h[obj.key] || []).concat(obj);
  return h; 
}, {})

Orijinal soru, arabaların markaya göre nasıl gruplanacağını sorar, ancak her gruptaki markayı atlar. Yani cevap şöyle görünecektir:

result = cars.reduce((h, {model,year,make}) => {
  return Object.assign(h, { [make]:( h[make] || [] ).concat({model,year})})
}, {})

Bu kesinlikle es5 değil
Shinigami

Sadece çalışıyor !. Herhangi biri bu azaltma işlevini ayrıntılı olarak açıklayabilir mi?
Jeevan

Ben her iki cevap sevdim, ama her ikisi de "make" alanı her "make" dizisinin bir üyesi olarak görüyorum. İletilen çıktının beklenen çıktıyla eşleştiği yere göre bir yanıt sağladım. Teşekkürler!
Daniel Vukasovich

15

groupByKodun genelleştirilmesi olan kendi fonksiyonunuz: https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore

function groupBy(xs, f) {
  return xs.reduce((r, v, i, a, k = f(v)) => ((r[k] || (r[k] = [])).push(v), r), {});
}

const 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' }];

const result = groupBy(cars, (c) => c.make);
console.log(result);


15

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'
}].reduce((r, car) => {

  const {
    model,
    year,
    make
  } = car;

  r[make] = [...r[make] || [], {
    model,
    year
  }];

  return r;
}, {});

console.log(cars);


8

Ayrılmak istiyorum REAL GROUP BYtam olarak aynı JS Diziler örneğin bu görevi burada

const inputArray = [ 
    { Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" },
    { Phase: "Phase 1", Step: "Step 1", Task: "Task 2", Value: "10" },
    { Phase: "Phase 1", Step: "Step 2", Task: "Task 1", Value: "15" },
    { Phase: "Phase 1", Step: "Step 2", Task: "Task 2", Value: "20" },
    { Phase: "Phase 2", Step: "Step 1", Task: "Task 1", Value: "25" },
    { Phase: "Phase 2", Step: "Step 1", Task: "Task 2", Value: "30" },
    { Phase: "Phase 2", Step: "Step 2", Task: "Task 1", Value: "35" },
    { Phase: "Phase 2", Step: "Step 2", Task: "Task 2", Value: "40" }
];

var outObject = inputArray.reduce(function(a, e) {
  // GROUP BY estimated key (estKey), well, may be a just plain key
  // a -- Accumulator result object
  // e -- sequentally checked Element, the Element that is tested just at this itaration

  // new grouping name may be calculated, but must be based on real value of real field
  let estKey = (e['Phase']); 

  (a[estKey] ? a[estKey] : (a[estKey] = null || [])).push(e);
  return a;
}, {});

console.log(outObject);


7

_.GroupBy işleviyle yineleme başına çağrılan işlevin içindeki nesneyi değiştirmeyi deneyebilirsiniz. Kaynak dizinin öğelerini değiştirdiğine dikkat edin!

var res = _.groupBy(cars,(car)=>{
    const makeValue=car.make;
    delete car.make;
    return makeValue;
})
console.log(res);
console.log(cars);

1
Bu kod soruyu çözebilir, ancak bunun sorunun nasıl ve neden çözüldüğüne dair bir açıklama da dahil olmak üzere , yayınınızın kalitesini artırmaya yardımcı olacaktır. Sadece şu anda soran kişi için değil, gelecekte okuyucular için soruyu cevapladığınızı unutmayın! Lütfen açıklama eklemek için cevabınızı düzenleyin ve hangi sınırlamaların ve varsayımların geçerli olduğunu belirtin.
Makyen

İstediğiniz sonucu elde etmek için diziye sadece bir kez gittiğiniz için bana en iyi cevap gibi görünüyor. makeÖzelliği kaldırmak için başka bir işlev kullanmaya gerek yoktur ve daha okunabilirdir.
Carrm

7

Basit bir fordöngü ile de mümkündür :

 const result = {};

 for(const {make, model, year} of cars) {
   if(!result[make]) result[make] = [];
   result[make].push({ model, year });
 }

Ve muhtemelen daha hızlı ve daha basit. Yazmak istemediğim bir db tablosundaki alanların uzun bir listesine sahip olduğumdan snippet'inizi biraz daha dinamik olacak şekilde genişlettim. Ayrıca const'u let ile değiştirmeniz gerekecektir. for ( let { TABLE_NAME, ...fields } of source) { result[TABLE_NAME] = result[TABLE_NAME] || []; result[TABLE_NAME].push({ ...fields }); }
adrien

TIL, teşekkürler! medium.com/@mautayro/…
adrien

5

Anahtarın boş olabileceği ve bunları başkaları olarak gruplandırmak istediğimiz durumlar için

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'},
            {'make':'kia','model':'optima','year':'2033'},
            {'make':null,'model':'zen','year':'2012'},
            {'make':null,'model':'blue','year':'2017'},

           ];


 result = cars.reduce(function (r, a) {
        key = a.make || 'others';
        r[key] = r[key] || [];
        r[key].push(a);
        return r;
    }, Object.create(null));

4

Yeniden kullanılabilecek bir yöntem oluşturma

Array.prototype.groupBy = function(prop) {
      return this.reduce(function(groups, item) {
        const val = item[prop]
        groups[val] = groups[val] || []
        groups[val].push(item)
        return groups
      }, {})
    };

Sonra aşağıda herhangi bir kritere göre gruplayabilirsiniz

const groupByMake = cars.groupBy('make');
        console.log(groupByMake);

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'
    },
];
  //re-usable method
Array.prototype.groupBy = function(prop) {
	  return this.reduce(function(groups, item) {
		const val = item[prop]
		groups[val] = groups[val] || []
		groups[val].push(item)
		return groups
	  }, {})
	};
  
 // initiate your groupBy. Notice the recordset Cars and the field Make....
  const groupByMake = cars.groupBy('make');
		console.log(groupByMake);
    
    //At this point we have objects. You can use Object.keys to return an array


3

ES6 kullanan prototip versiyonu. Temel olarak bu, bir akümülatör ve geçerli öğeyi iletmek için azaltma işlevini kullanır; bu, daha sonra, geçirilen anahtara göre "gruplandırılmış" dizilerinizi oluşturmak için bunu kullanır. indirgeme işleminin iç kısmı karmaşık görünebilir, ancak esasen iletilen nesnenin anahtarının var olup olmadığını ve daha sonra boş bir dizi oluşturup oluşturmadığını ve geçerli öğeyi yeni oluşturulmuş diziye eklemesini kontrol etmek için test eder. operatör geçerli anahtar dizisinin tüm nesnelerini geçirir ve geçerli öğeyi ekler. Umarım bu birine yardımcı olur !.

Array.prototype.groupBy = function(k) {
  return this.reduce((acc, item) => ((acc[item[k]] = [...(acc[item[k]] || []), item]), acc),{});
};

const projs = [
  {
    project: "A",
    timeTake: 2,
    desc: "this is a description"
  },
  {
    project: "B",
    timeTake: 4,
    desc: "this is a description"
  },
  {
    project: "A",
    timeTake: 12,
    desc: "this is a description"
  },
  {
    project: "B",
    timeTake: 45,
    desc: "this is a description"
  }
];

console.log(projs.groupBy("project"));

1

Bunun array#forEach()gibi bir yöntem de kullanabilirsiniz :

const 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' }];

let newcars = {}

cars.forEach(car => {
  newcars[car.make] ? // check if that array exists or not in newcars object
    newcars[car.make].push({model: car.model, year: car.year})  // just push
   : (newcars[car.make] = [], newcars[car.make].push({model: car.model, year: car.year})) // create a new array and push
})

console.log(newcars);


1
function groupBy(data, property) {
  return data.reduce((acc, obj) => {
    const key = obj[property];
    if (!acc[key]) {
      acc[key] = [];
    }
    acc[key].push(obj);
    return acc;
  }, {});
}
groupBy(people, 'age');

1

Sadece bunu deneyin, benim için iyi çalışıyor.

let grouped = _.groupBy(cars, 'make');


2
Yakalanmayan ReferenceError: _ tanımlı değil - çözümünüzün bunu çözmek için bir 3. taraf kitaplığı yüklemesi gerektiğini açıkça belirtmelisiniz.
metakungfu

1
üzgünüm, bence herkes biliyor. _ standları ve çoğunlukla lodash lib için kullanılır. bu yüzden lodash kullanmanız gerekiyor. lütfen soruyu okuyun, böylelikle onun sersemletmek istediğini anlarsınız. teşekkürler. bunu hatırlayacağım. ve lib yazmayı asla unutma.
agravat.10

1

Harici kütüphaneler kullanmayan her çözümün performansını test etmek için bir kıyaslama yaptım.

JSBen.ch

reduce()@Nina Scholz tarafından gönderildi seçenek, en uygun biri olarak görünüyor.


0

@Metakunfu cevabını beğendim, ancak beklenen çıktıyı tam olarak vermiyor. İşte son JSON yükünde "make" den kurtulmak için güncellenmiş bir güncellemedir.

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'
    },
];

result = cars.reduce((h, car) => Object.assign(h, { [car.make]:( h[car.make] || [] ).concat({model: car.model, year: car.year}) }), {})

console.log(JSON.stringify(result));

Çıktı:

{  
   "audi":[  
      {  
         "model":"r8",
         "year":"2012"
      },
      {  
         "model":"rs5",
         "year":"2013"
      }
   ],
   "ford":[  
      {  
         "model":"mustang",
         "year":"2012"
      },
      {  
         "model":"fusion",
         "year":"2015"
      }
   ],
   "kia":[  
      {  
         "model":"optima",
         "year":"2012"
      }
   ]
}

0

Lodash / fp ile _.flow()bir anahtarla 1. gruplarla bir işlev oluşturabilir ve ardından her grubu eşleyebilir ve her öğeden bir anahtarı atlayabilirsiniz:

const { flow, groupBy, mapValues, map, omit } = _;

const groupAndOmitBy = key => flow(
  groupBy(key),
  mapValues(map(omit(key)))
);

const 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' }];

const groupAndOmitMake = groupAndOmitBy('make');

const result = groupAndOmitMake(cars);

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src='https://cdn.jsdelivr.net/g/lodash@4(lodash.min.js+lodash.fp.min.js)'></script>


0

Tüm alanlarınızı yazmak istemiyorsanız @Jonas_Wilms tarafından verilen cevaba dayanarak:

    var result = {};

    for ( let { first_field, ...fields } of your_data ) 
    { 
       result[first_field] = result[first_field] || [];
       result[first_field].push({ ...fields }); 
    }

Herhangi bir kıyaslama yapmadım ama for döngüsü kullanmanın bu cevapta önerilen herhangi bir şeyden daha verimli olacağına inanıyorum .


0
const reGroup = (list, key) => {
    const newGroup = {};
    list.forEach(item => {
        const newItem = Object.assign({}, item);
        delete newItem[key];
        newGroup[item[key]] = newGroup[item[key]] || [];
        newGroup[item[key]].push(newItem);
    });
    return newGroup;
};
const animals = [
  {
    type: 'dog',
    breed: 'puddle'
  },
  {
    type: 'dog',
    breed: 'labradoodle'
  },
  {
    type: 'cat',
    breed: 'siamese'
  },
  {
    type: 'dog',
    breed: 'french bulldog'
  },
  {
    type: 'cat',
    breed: 'mud'
  }
];
console.log(reGroup(animals, 'type'));
const 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'
  },
];

console.log(reGroup(cars, 'make'));

0

Bununla birlikte grup yazı tipinde Gruplanmış Nesne Dizisi:

groupBy (list: any[], key: string): Map<string, Array<any>> {
    let map = new Map();
    list.map(val=> {
        if(!map.has(val[key])){
            map.set(val[key],list.filter(data => data[key] == val[key]));
        }
    });
    return map;
});

Her anahtar için bir arama yaptığınızda bu verimsiz görünüyor. Arama büyük olasılıkla O (n) karmaşıklığına sahiptir.
Leukipp

0

Ben hiçbir bağımlılık / karmaşıklık sadece saf basit js ile yazmayı seviyorum.

const mp = {}
const cars = [
  {
    model: 'Imaginary space craft SpaceX model',
    year: '2025'
  },
  {
    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'
  }
]

cars.forEach(c => {
  if (!c.make) return // exit (maybe add them to a "no_make" category)

  if (!mp[c.make]) mp[c.make] = [{ model: c.model, year: c.year }]
  else mp[c.make].push({ model: c.model, year: c.year })
})

console.log(mp)


-1

İşte başka bir çözüm. İstendiği gibi.

Yapmak tarafından gruplandırılmış araba nesneleri yeni bir dizi yapmak istiyorum:

function groupBy() {
  const key = 'make';
  return cars.reduce((acc, x) => ({
    ...acc,
    [x[key]]: (!acc[x[key]]) ? [{
      model: x.model,
      year: x.year
    }] : [...acc[x[key]], {
      model: x.model,
      year: x.year
    }]
  }), {})
}

Çıktı:

console.log('Grouped by make key:',groupBy())

-1

Java'da Collectors.groupingBy () yönteminden esinlenen bir çözüm:

function groupingBy(list, keyMapper) {
  return list.reduce((accummalatorMap, currentValue) => {
    const key = keyMapper(currentValue);
    if(!accummalatorMap.has(key)) {
      accummalatorMap.set(key, [currentValue]);
    } else {
      accummalatorMap.set(key, accummalatorMap.get(key).push(currentValue));
    }
    return accummalatorMap;
  }, new Map());
}

Bu bir Map nesnesi verecektir.

// Usage

const carMakers = groupingBy(cars, car => car.make);

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.