Cách nhóm một mảng các đối tượng theo khóa


153

Có ai biết một cách (lodash nếu có thể) để nhóm một mảng các đối tượng theo một khóa đối tượng sau đó tạo ra một mảng các đối tượng mới dựa trên việc phân nhóm không? Ví dụ, tôi có một loạt các đối tượng xe hơi:

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

Tôi muốn tạo ra một loạt các đối tượng xe hơi mới được nhóm theo 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
Bạn đã nhìn vào groupBy?
SLaks

2
kết quả của bạn không hợp lệ.
Nina Scholz

Có một cách tiếp cận tương tự để có được một Bản đồ thay vì một đối tượng?
Andrea Bergonzo

Câu trả lời:


104

Câu trả lời của Timo là cách tôi sẽ làm điều đó. Đơn giản _.groupByvà cho phép một số trùng lặp trong các đối tượng trong cấu trúc được nhóm.

Tuy nhiên, OP cũng yêu cầu makeloại bỏ các phím trùng lặp . Nếu bạn muốn đi tất cả các cách:

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

console.log(grouped);

Sản lượng:

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

Nếu bạn muốn làm điều này bằng Underscore.js, lưu ý rằng phiên bản của _.mapValuesnó được gọi _.mapObject.


278

Trong Javascript đơn giản, bạn có thể sử dụng Array#reducevới một đối tượng

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
Làm thế nào tôi có thể lặp lại resultkết quả?
Mounir Elfassi

1
bạn có thể lấy các mục với Object.entriesvà lặp qua các cặp khóa / giá trị.
Nina Scholz

Có cách nào để loại bỏ makekhỏi tập dữ liệu một khi được nhóm không? Đó là chiếm thêm không gian.
Mercurial


R và đứng để làm gì? Sẽ là chính xác khi giả sử rằng r là bộ tích lũy và giá trị hiện tại?
Omar

68

Bạn đang tìm kiếm _.groupBy().

Việc xóa tài sản mà bạn đang nhóm khỏi các đối tượng sẽ là chuyện nhỏ nếu được yêu cầu:

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>


Là một phần thưởng, bạn nhận được cú pháp thậm chí còn đẹp hơn với các hàm mũi tên ES6:

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

18
Và nếu bạn muốn nó vẫn ngắn hơn, var grouped = _.groupBy(cars, 'make');Không cần một chức năng nào cả, nếu người truy cập là một tên thuộc tính đơn giản.
Jonathan Eunice

1
Chữ '_' là viết tắt của từ gì?
Adrian Grzywaczewski

@AdrianGrzywaczewski đó là quy ước mặc định cho khoảng cách tên 'lodash' hoặc 'gạch dưới'. Bây giờ các librairies là mô-đun, nó không còn cần thiết, tức là. npmjs.com/package/lodash.groupby
vilsbole

5
Và làm thế nào tôi có thể tương tác trong kết quả?
Luis Antonio Pestana

36

Phiên bản ngắn để nhóm một mảng các đối tượng theo một khóa nhất định trong es6:

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

Phiên bản dài hơn:

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

Nó xuất hiện câu hỏi ban đầu hỏi làm thế nào để nhóm xe bằng cách thực hiện, nhưng bỏ qua việc thực hiện trong mỗi nhóm. Vì vậy, câu trả lời sẽ như thế này:

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

đây chắc chắn không phải là es5
Shinigami

Nó chỉ hoạt động! Bất kỳ ai có thể xây dựng chức năng giảm này?
Jeevan

Tôi thích cả hai câu trả lời của bạn, nhưng tôi thấy cả hai đều cung cấp trường "make" với tư cách là thành viên của mỗi mảng "make". Tôi đã cung cấp câu trả lời dựa trên câu hỏi của bạn nơi đầu ra được phân phối khớp với đầu ra dự kiến. Cảm ơn!
Daniel Vukasovich

15

Đây là của riêng bạn groupBy chức năng , đó là sự khái quát hóa mã từ: 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

Tôi sẽ để lại REAL GROUP BYví dụ về Mảng JS giống hệt nhiệm vụ này ở đây

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

Bạn có thể cố gắng sửa đổi đối tượng bên trong hàm được gọi là mỗi lần lặp bởi _.groupBy func. Lưu ý rằng mảng nguồn thay đổi các yếu tố của mình!

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

1
Mặc dù mã này có thể giải quyết câu hỏi, bao gồm giải thích về cách thức và lý do giải quyết vấn đề này thực sự sẽ giúp cải thiện chất lượng bài đăng của bạn. Hãy nhớ rằng bạn đang trả lời câu hỏi cho độc giả trong tương lai, không chỉ là người hỏi bây giờ! Vui lòng chỉnh sửa câu trả lời của bạn để thêm giải thích và đưa ra dấu hiệu về những hạn chế và giả định được áp dụng.
Makyen

Có vẻ như câu trả lời tốt nhất cho tôi vì bạn chỉ đi qua mảng một lần để có được kết quả mong muốn. Không cần sử dụng chức năng khác để xóa thuộc maketính và nó cũng dễ đọc hơn.
Carrm

7

Nó cũng có thể với một forvòng lặp đơn giản :

 const result = {};

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

Và có lẽ nhanh hơn là tốt, và đơn giản hơn. Tôi đã mở rộng đoạn mã của bạn trở nên năng động hơn một chút vì tôi có một danh sách dài các trường từ bảng db mà tôi không muốn nhập. Ngoài ra, bạn cần lưu ý thay thế const bằng let. for ( let { TABLE_NAME, ...fields } of source) { result[TABLE_NAME] = result[TABLE_NAME] || []; result[TABLE_NAME].push({ ...fields }); }
adrien


5

Đối với trường hợp khóa có thể là null và chúng tôi muốn nhóm chúng thành những người khác

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

Tạo một phương thức có thể được sử dụng lại

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
      }, {})
    };

Sau đó, bên dưới bạn có thể nhóm theo bất kỳ tiêu chí

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

Phiên bản nguyên mẫu sử dụng ES6 là tốt. Về cơ bản, điều này sử dụng hàm rút gọn để truyền vào một bộ tích lũy và mục hiện tại, sau đó sử dụng hàm này để xây dựng các mảng "được nhóm" của bạn dựa trên khóa được truyền vào. phần bên trong của giảm có thể trông phức tạp nhưng về cơ bản, nó đang thử nghiệm xem liệu khóa của đối tượng được truyền có tồn tại không và nếu nó không tạo ra một mảng trống và nối thêm mục hiện tại vào mảng mới được tạo bằng cách khác toán tử vượt qua trong tất cả các đối tượng của mảng khóa hiện tại và nối thêm mục hiện tại. Hy vọng điều này sẽ giúp được ai đó!.

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

Bạn cũng có thể sử dụng array#forEach()phương thức như thế này:

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

Chỉ cần thử cái này nó hoạt động tốt cho tôi.

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


2
Uncaught ReferenceError: _ không được xác định - bạn nên rõ ràng rằng giải pháp của bạn yêu cầu cài đặt thư viện của bên thứ 3 chỉ để giải quyết vấn đề này.
metakungfu

1
xin lỗi, tôi nghĩ rằng mọi người đều biết _ đứng và chủ yếu được sử dụng cho lib lodash. vì vậy bạn cần sử dụng lodash. xin vui lòng đọc câu hỏi để bạn sẽ biết anh ấy / cô ấy đang yêu cầu lodash. vậy, cám ơn. tôi sẽ nhớ điều này. và không bao giờ quên viết lib.
agravat.in

1

Tôi đã tạo một điểm chuẩn để kiểm tra hiệu suất của từng giải pháp không sử dụng các thư viện bên ngoài.

JSBen.ch

Các reduce()tùy chọn, đăng tải bởi @Nina Scholz dường như là một tối ưu.


0

Tôi thích câu trả lời @metakunfu, nhưng nó không cung cấp chính xác đầu ra dự kiến. Đây là một bản cập nhật loại bỏ "make" trong tải trọng JSON cuối cùng.

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

Đầu ra:

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

0

Với lodash / fp, bạn có thể tạo một hàm với _.flow()các nhóm thứ nhất đó bằng một phím, sau đó ánh xạ từng nhóm và bỏ qua một khóa từ mỗi mục:

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

Dựa trên câu trả lời của @Jonas_Wilms nếu bạn không muốn nhập vào tất cả các trường của mình:

    var result = {};

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

Tôi đã không thực hiện bất kỳ điểm chuẩn nào nhưng tôi tin rằng việc sử dụng vòng lặp for sẽ hiệu quả hơn bất kỳ điều gì được đề xuất trong câu trả lời này .


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

Nhóm đối tượng được nhóm trong bản thảo với điều này:

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

Điều này có vẻ không hiệu quả khi bạn thực hiện tìm kiếm cho mỗi khóa. Việc tìm kiếm có nhiều khả năng là độ phức tạp của O (n).
Leukipp

0

Tôi thích viết nó mà không phụ thuộc / phức tạp chỉ là js đơn giản thuần túy.

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

Đây là một giải pháp khác cho nó. Như yêu cầu.

Tôi muốn tạo ra một loạt các đối tượng xe hơi mới được nhóm bằng:

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
    }]
  }), {})
}

Đầu ra:

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

-1

Đây là một giải pháp được lấy cảm hứng từ Collector.groupingBy () trong Java:

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

Điều này sẽ cung cấp cho một đối tượng Map.

// Usage

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.