31

How to convert this:

[
    {food: 'apple', type: 'fruit'},
    {food: 'potato', type: 'vegetable'},
    {food: 'banana', type: 'fruit'},
]

into this:

[
    {type: 'fruit', foods: ['apple', 'banana']},
    {type: 'vegetable', foods: ['potato']}
]

using javascript or underscore

5
  • 2
    @Harley It's best to show what you've tried. Succinct is not as important as showing effort. It's not just an idle request, either--seeing where you are with it gives us a better shot at creating something exactly at the needed level. Commented Apr 8, 2013 at 20:23
  • 1
    @ErikE Alternatively, this is a nice and simple question that has high googability, with no such nonsense as failed and convoluted attempts of a solution. This is the type of question that will get the most up-votes because its generic and the answer will be as well, ultimately benefitting everyone. Commented Feb 19, 2016 at 21:34
  • @SSHThis To each his own perspective, though some perspectives may be more in line with reality than others. I think you will find once you participate on this site longer that showing effort is an important part of the community's values... Commented Feb 19, 2016 at 21:54
  • 1
    @ErikE No need to be snarky ... again Commented Feb 19, 2016 at 23:25
  • @SSHThis I have a factual disagreement with you, that is all! Commented Feb 20, 2016 at 0:04

8 Answers 8

91

Assuming the original list is contained in a variable named list:

_
.chain(list)
.groupBy('type')
.map(function(value, key) {
    return {
        type: key,
        foods: _.pluck(value, 'food')
    }
})
.value();
Sign up to request clarification or add additional context in comments.

2 Comments

Who needs lengthy SQL when you have underscore + mongoDB!?
Whoever needs their code to perform well and avoid doing data query operations on RAM and wants to make use of the database server.
17

Without using underscore:

var origArr = [
    {food: 'apple', type: 'fruit'},
    {food: 'potato', type: 'vegetable'},
    {food: 'banana', type: 'fruit'}
];

/*[
    {type: 'fruit', foods: ['apple', 'banana']},
    {type: 'vegetable', foods: ['potato']}
]*/

function transformArr(orig) {
    var newArr = [],
        types = {},
        i, j, cur;
    for (i = 0, j = orig.length; i < j; i++) {
        cur = orig[i];
        if (!(cur.type in types)) {
            types[cur.type] = {type: cur.type, foods: []};
            newArr.push(types[cur.type]);
        }
        types[cur.type].foods.push(cur.food);
    }
    return newArr;
}

console.log(transformArr(origArr));

DEMO: http://jsfiddle.net/ErikE/nSLua/3/

Credit goes to @ErikE for improving/reducing my original code to help with redundancy I had :)

7 Comments

Suggestion: put newArr.push(types[cur.type]); inside your if block and remove the second for loop entirely.
@ErikE Yeah, I'm already refactoring. I know the double loop isn't good, so I went back. Hold on, maybe what I'm doing is what you're saying.
@ErikE I think it took a little more than what you suggested, but I already forget what it used to look like. It seems to work, and doesn't require 2 loops, so it seems fine :)
The index indirection is not needed. I find the new code awkward. Please see this.
@ErikE Ahh yes, I'm fooled again. I forget that doing it that way, the new object is pushed by reference (which I didn't realize), so you can modify the original (in types) and it will be reflected in newArr. Thanks for pointing that out! Though I'd hardly call my old code awkward...it made sense as a "lookup" to me at least :)
|
6

Here is a slightly different but more generic version of @Ian's answer

Caveat: the result is slightly different from the OP requirement, but others like me who stumble on this question might benefit from a more generic answer IMHO

var origArr = [
   {food: 'apple', type: 'fruit'},
   {food: 'potato', type: 'vegetable'},
   {food: 'banana', type: 'fruit'}
];

function groupBy(arr, key) {
        var newArr = [],
            types = {},
            newItem, i, j, cur;
        for (i = 0, j = arr.length; i < j; i++) {
            cur = arr[i];
            if (!(cur[key] in types)) {
                types[cur[key]] = { type: cur[key], data: [] };
                newArr.push(types[cur[key]]);
            }
            types[cur[key]].data.push(cur);
        }
        return newArr;
}

console.log(groupBy(origArr, 'type'));

You can find a jsfiddle here

2 Comments

Hi there. This is exactly what I'm looking for! Thank you! -- I think it would be a good idea to show what console.log prints out.
Unused variable newItem and j. Why?
3

An ES6 solution to this old question:

Iterate using Array#reduce, and collect the items by group into a Map. Use spread to convert the Map#values back into array:

const data = [
    {food: 'apple', type: 'fruit'},
    {food: 'potato', type: 'vegetable'},
    {food: 'banana', type: 'fruit'},
];

const result = [...data.reduce((hash, { food, type }) => {
  const current = hash.get(type) || { type, foods: [] };
  
  current.foods.push({ food });
  
  return hash.set(type, current);
}, new Map).values()];

console.log(result);

Comments

2
var foods = [
    {food: 'apple', type: 'fruit'},
    {food: 'potato', type: 'vegetable'},
    {food: 'banana', type: 'fruit'}
];

var newFoods = _.chain( foods ).reduce(function( memo, food ) {
  memo[ food.type ] = memo[ food.type ] || [];
  memo[ food.type ].push( food.food );
  return memo;
}, {}).map(function( foods, type ) {
    return {
        type: type,
        foods: foods
    };
}).value();

http://jsbin.com/etaxih/2/edit

Comments

0

You can group array of objects by one of fields with Alasql library. This example compact arrays exactly as in your example:

var res = alasql('SELECT type, ARRAY(food) AS foods FROM ? GROUP BY type',[food]);

Try this example at jsFiddle.

2 Comments

hi agershum, i tried do what you wrote on an array created fron ajax but wont work result are undefined. But if i try console.log(array[0].property); data in cosolle are ok
hi agershum, i tried do what you wrote on an array created fron ajax but wont work. Result are allways undefined. example: var array=[{ ​​ status: undefined student_id: "32" ​​ student_name: "Marco Bruno" ​​ student_year_id: "2", status: absent } ... to 500 record ] But if i try console.log(array[0].property); data in cosolle are ok. I want to count student_id and group by status thanks
0

You could also use other ES6 Features such as:

function groupArray(arr, groupBy, keepProperty) {
        let rArr = [], i;
        arr.forEach(item => {
            if((i = rArr.findIndex(obj => obj[groupBy] === item[groupBy])) !== -1)
                rArr[i][`${keepProperty}s`].push(item[keepProperty]);
            else rArr.push({
                [groupBy]: item[groupBy],
                [`${keepProperty}s`]: [item[keepProperty]]
            });
        });
        return rArr;
    }

    groupArray(yourArray, 'type', 'food');

Comments

0

You can achieve this by using Array.group() method along with Array.forEach() which is currently in Experimental stage. I will suggest you to Check the Browser compatibility table carefully before using this in production.

Below code snippet will only work in Firefox Nightly as currently Array.group() does not have support for any other browsers.

// Input array
const inventory = [
    {food: 'apple', type: 'fruit'},
    {food: 'potato', type: 'vegetable'},
    {food: 'banana', type: 'fruit'},
];

// Return the object grouped with type property.
const result = inventory.group(({ type }) => type);

// declaring an empty array to store the final results.
const arr = [];

// Iterating the group object to structure the final result.
Object.keys(result).forEach((item, index) => {
  result[item] = result[item].map(({ food }) => food);
  arr.push({
    type: item,
    foods: result[item]
  })
});

// Output
console.log(arr);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.