객체 배열을 그룹화하는 가장 효율적인 방법


507

배열에서 객체를 그룹화하는 가장 효율적인 방법은 무엇입니까?

예를 들어,이 객체 배열이 주어진 경우 :

[ 
    { 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" }
]

이 정보를 표에 표시하고 있습니다. 다른 방법으로 그룹화하고 싶지만 값을 합산하고 싶습니다.

Underscore.js를 groupby 함수에 사용하고 있습니다. 이는 도움이되지만 SQL group by메서드 와 같이 "분할"하지만 "병합"되기를 원하지 않기 때문에 모든 트릭을 수행하지는 않습니다 .

내가 찾고있는 것은 특정 값을 합계 할 수있을 것입니다 (요청한 경우).

따라서 groupby를 수행 Phase하면 다음을 수신하고 싶습니다.

[
    { Phase: "Phase 1", Value: 50 },
    { Phase: "Phase 2", Value: 130 }
]

그리고 groupy Phase/를 수행 하면 다음과 같은 메시지가 나타납니다 Step.

[
    { Phase: "Phase 1", Step: "Step 1", Value: 15 },
    { Phase: "Phase 1", Step: "Step 2", Value: 35 },
    { Phase: "Phase 2", Step: "Step 1", Value: 55 },
    { Phase: "Phase 2", Step: "Step 2", Value: 75 }
]

이에 유용한 스크립트가 있습니까, 아니면 Underscore.js를 사용하고 결과 객체를 반복하여 총계를 직접 수행해야합니까?

답변:


755

외부 라이브러리를 피하려면 groupBy()다음과 같은 바닐라 버전을 간결하게 구현할 수 있습니다 .

var groupBy = function(xs, key) {
  return xs.reduce(function(rv, x) {
    (rv[x[key]] = rv[x[key]] || []).push(x);
    return rv;
  }, {});
};

console.log(groupBy(['one', 'two', 'three'], 'length'));

// => {3: ["one", "two"], 5: ["three"]}


18
```return xs.reduce (function (rv, x) {var v = key instanceof Function? key (x) : x [key]; (rv [v] = rv [v] || []). push (x); return rv;}, {}); ```콜백 함수가 정렬 기준을 반환하도록 허용
y_nk

110
다음은 객체가 아닌 배열을 출력하는 것입니다. groupByArray (xs, key) {return xs.reduce (function (rv, x) {let v = key instanceof Function? key (x) : x [key]; let el = rv .find ((r) => r && r.key === v); if (el) {el.values.push (x);} else {rv.push ({키 : v, 값 : [x] });} return rv;}, []); }
tomitrescak

24
좋아, 내가 필요한 것만 경우 다른 사람이 그것을 필요에서, 여기에 타이프 라이터 서명입니다 :var groupBy = function<TItem>(xs: TItem[], key: string) : {[key: string]: TItem[]} { ...
마이클 산디노

4
그 가치는 무엇이든, find ()는 아마도 O (n)이므로 tomitrescak의 솔루션은 편리하지만 효율성이 크게 떨어집니다. 답의 해는 O (n)입니다. 감소에서 (객체 할당은 O (1)입니다. 푸시처럼) 주석은 O (n) * O (n) 또는 O (n ^ 2)입니다. 최소 O (nlgn)
narthur157

21
사람이 관심이 있다면,이 기능의 가독성 및 주석 버전을 만들어 요지에 넣어 : gist.github.com/robmathers/1830ce09695f759bf2c4df15c29dd22d 실제로 여기에 무슨 일이 일어나고 있는지 이해가 도움이되었다고합니다.
robmathers

228

ES6 Map 객체 사용 :

function groupBy(list, keyGetter) {
    const map = new Map();
    list.forEach((item) => {
         const key = keyGetter(item);
         const collection = map.get(key);
         if (!collection) {
             map.set(key, [item]);
         } else {
             collection.push(item);
         }
    });
    return map;
}

// example usage

const pets = [
    {type:"Dog", name:"Spot"},
    {type:"Cat", name:"Tiger"},
    {type:"Dog", name:"Rover"}, 
    {type:"Cat", name:"Leo"}
];
    
const grouped = groupBy(pets, pet => pet.type);
    
console.log(grouped.get("Dog")); // -> [{type:"Dog", name:"Spot"}, {type:"Dog", name:"Rover"}]
console.log(grouped.get("Cat")); // -> [{type:"Cat", name:"Tiger"}, {type:"Cat", name:"Leo"}]
    
    

지도 정보 : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map


@mortb, get()메소드 를 호출하지 않고 얻는 방법? 키를 전달하지 않고 출력이 표시되기를 원합니다
Fai Zal Dong

@FaiZalDong : 귀하의 사례에 가장 적합한 것이 무엇인지 잘 모르겠습니다. console.log(grouped.entries());jsfiddle 예제를 작성하면 키 + 값 배열처럼 동작하는 iterable을 반환합니다. 당신은 그것을 시도하고 그것이 도움이되는지 볼 수 있습니까?
mortb

7
시도해 볼 수도 있습니다console.log(Array.from(grouped));
mortb

나는 매우 유연한이 답변을 좋아합니다
benshabatnoam

그룹의 요소 수를 보려면 :Array.from(groupBy(jsonObj, item => i.type)).map(i => ( {[i[0]]: i[1].length} ))
Ahmet Şimşek

105

ES6 사용시 :

const groupBy = (items, key) => items.reduce(
  (result, item) => ({
    ...result,
    [item[key]]: [
      ...(result[item[key]] || []),
      item,
    ],
  }), 
  {},
);

3
익숙해 지려면 약간의 시간이 걸리지 만 대부분의 C ++ 템플릿도 마찬가지입니다.
Levi Haskell

2
나는 나의 두뇌를 깨뜨 렸고 여전히 세상에서 어떻게 작동하는지 이해하지 못했습니다 ...result. 이제는 잠을 잘 수 없습니다.

8
우아하지만 큰 어레이에서는 고통스럽게 느립니다!
infinity1975

4
lol이게 뭐야
Nino Škopac

1
쉬운 해결책. 감사합니다
Ezequiel Tavares

59

있지만 LINQ의 대답이 재미있다, 그것은 또한 아주 무거운 무게입니다. 내 접근 방식은 다소 다릅니다.

var DataGrouper = (function() {
    var has = function(obj, target) {
        return _.any(obj, function(value) {
            return _.isEqual(value, target);
        });
    };

    var keys = function(data, names) {
        return _.reduce(data, function(memo, item) {
            var key = _.pick(item, names);
            if (!has(memo, key)) {
                memo.push(key);
            }
            return memo;
        }, []);
    };

    var group = function(data, names) {
        var stems = keys(data, names);
        return _.map(stems, function(stem) {
            return {
                key: stem,
                vals:_.map(_.where(data, stem), function(item) {
                    return _.omit(item, names);
                })
            };
        });
    };

    group.register = function(name, converter) {
        return group[name] = function(data, names) {
            return _.map(group(data, names), converter);
        };
    };

    return group;
}());

DataGrouper.register("sum", function(item) {
    return _.extend({}, item.key, {Value: _.reduce(item.vals, function(memo, node) {
        return memo + Number(node.Value);
    }, 0)});
});

JSBin에서 실제로 볼 수 있습니다 .

Underscore에서 has누락 된 내용 이 있지만 그 기능을 수행하는 것을 보지 못했습니다 . 와 거의 동일 _.contains하지만 비교 _.isEqual보다는 사용합니다 ===. 그 외에는 일반적인 시도가 있지만 나머지는 문제에 따라 다릅니다.

이제 DataGrouper.sum(data, ["Phase"])반환

[
    {Phase: "Phase 1", Value: 50},
    {Phase: "Phase 2", Value: 130}
]

그리고 DataGrouper.sum(data, ["Phase", "Step"])반환

[
    {Phase: "Phase 1", Step: "Step 1", Value: 15},
    {Phase: "Phase 1", Step: "Step 2", Value: 35},
    {Phase: "Phase 2", Step: "Step 1", Value: 55},
    {Phase: "Phase 2", Step: "Step 2", Value: 75}
]

그러나 sum여기서는 하나의 잠재적 기능 일뿐입니다. 원하는대로 다른 사람을 등록 할 수 있습니다.

DataGrouper.register("max", function(item) {
    return _.extend({}, item.key, {Max: _.reduce(item.vals, function(memo, node) {
        return Math.max(memo, Number(node.Value));
    }, Number.NEGATIVE_INFINITY)});
});

이제 DataGrouper.max(data, ["Phase", "Step"])돌아올 것이다

[
    {Phase: "Phase 1", Step: "Step 1", Max: 10},
    {Phase: "Phase 1", Step: "Step 2", Max: 20},
    {Phase: "Phase 2", Step: "Step 1", Max: 30},
    {Phase: "Phase 2", Step: "Step 2", Max: 40}
]

또는 이것을 등록한 경우 :

DataGrouper.register("tasks", function(item) {
    return _.extend({}, item.key, {Tasks: _.map(item.vals, function(item) {
      return item.Task + " (" + item.Value + ")";
    }).join(", ")});
});

전화 DataGrouper.tasks(data, ["Phase", "Step"])하면 당신을 얻을 것이다

[
    {Phase: "Phase 1", Step: "Step 1", Tasks: "Task 1 (5), Task 2 (10)"},
    {Phase: "Phase 1", Step: "Step 2", Tasks: "Task 1 (15), Task 2 (20)"},
    {Phase: "Phase 2", Step: "Step 1", Tasks: "Task 1 (25), Task 2 (30)"},
    {Phase: "Phase 2", Step: "Step 2", Tasks: "Task 1 (35), Task 2 (40)"}
]

DataGrouper그 자체가 함수입니다. 데이터와 그룹화하려는 속성 목록으로 호출 할 수 있습니다. 요소가 두 개의 속성을 가진 객체 인 배열을 반환합니다. key그룹화 된 속성의 모음이고 vals키에없는 나머지 속성을 포함하는 객체의 배열입니다. 예를 들어 다음 DataGrouper(data, ["Phase", "Step"])을 산출합니다.

[
    {
        "key": {Phase: "Phase 1", Step: "Step 1"},
        "vals": [
            {Task: "Task 1", Value: "5"},
            {Task: "Task 2", Value: "10"}
        ]
    },
    {
        "key": {Phase: "Phase 1", Step: "Step 2"},
        "vals": [
            {Task: "Task 1", Value: "15"}, 
            {Task: "Task 2", Value: "20"}
        ]
    },
    {
        "key": {Phase: "Phase 2", Step: "Step 1"},
        "vals": [
            {Task: "Task 1", Value: "25"},
            {Task: "Task 2", Value: "30"}
        ]
    },
    {
        "key": {Phase: "Phase 2", Step: "Step 2"},
        "vals": [
            {Task: "Task 1", Value: "35"}, 
            {Task: "Task 2", Value: "40"}
        ]
    }
]

DataGrouper.register함수를 허용하고 그룹화 할 초기 데이터 및 특성을 승인하는 새 함수를 작성합니다. 이 새로운 함수는 위와 같이 출력 형식을 가져 와서 각각에 대해 함수를 실행하여 새로운 배열을 반환합니다. 생성 된 함수는 DataGrouper제공 한 이름 에 따라 속성으로 저장되며 로컬 참조를 원할 경우 반환됩니다.

글쎄요, 그것은 많은 설명입니다. 코드는 매우 간단합니다.


안녕하세요 .. 값으로 그룹화하고 합계를 볼 수 있지만 value1 및 valu2 및 value3으로 합계를 원할 경우 해결책이 있습니까?
사무엘 OSPINA

@SAMUELOSPINA이 방법을 찾은 적이 있습니까?
howMuchCheeseIsTooMuchCheese

50

lodash 그룹 을 확인 하여 원하는 것을 정확하게 수행하는 것 같습니다. 또한 매우 가볍고 간단합니다.

바이올린 예 : https://jsfiddle.net/r7szvt5k/

배열 이름이 arrgroupBy이면 lodash를 사용하면 다음과 같습니다.

import groupBy from 'lodash/groupBy';
// if you still use require:
// const groupBy = require('lodash/groupBy');

const a = groupBy(arr, function(n) {
  return n.Phase;
});
// a is your array grouped by Phase attribute

1
이 답변이 문제가되지 않습니까? lodash _.groupBy 결과가 OP가 요청한 결과 형식이 아닌 여러 가지 방법이 있습니다. (1) 결과는 배열이 아닙니다. (2) "값"이 lodash 개체 결과에서 "키"가되었습니다.
mg1075

44

이것은 아마도 더 쉽게 이루어집니다 linq.js자바 스크립트에서 LINQ의 진정한 구현 (의도 된, 데모 ) :

var linq = Enumerable.From(data);
var result =
    linq.GroupBy(function(x){ return x.Phase; })
        .Select(function(x){
          return {
            Phase: x.Key(),
            Value: x.Sum(function(y){ return y.Value|0; })
          };
        }).ToArray();

결과:

[
    { Phase: "Phase 1", Value: 50 },
    { Phase: "Phase 2", Value: 130 }
]

또는 더 간단하게 문자열 기반 선택기를 사용하면 ( DEMO )를 사용하십시오.

linq.GroupBy("$.Phase", "",
    "k,e => { Phase:k, Value:e.Sum('$.Value|0') }").ToArray();

그룹화하는 동안 여러 속성을 사용할 수 GroupBy(function(x){ return x.Phase; })
Amit

37

에서 ES6 Map을 빌드 할 수 있습니다 array.reduce().

const groupedMap = initialArray.reduce(
    (entryMap, e) => entryMap.set(e.id, [...entryMap.get(e.id)||[], e]),
    new Map()
);

이것은 다른 솔루션보다 몇 가지 장점이 있습니다.

  • 라이브러리가 필요하지 않습니다 (예 : _.groupBy() )
  • Map객체가 아닌 자바 스크립트를받습니다 (예 :에서 반환 _.groupBy()). 이것은 많은 이점이 있습니다 .
    • 항목이 처음 추가 된 순서를 기억합니다.
    • 키는 문자열이 아닌 모든 유형이 될 수 있습니다.
  • A Map는 배열 배열보다 유용한 결과입니다. 그러나 배열 배열을 원하면 Array.from(groupedMap.entries())( [key, group array]쌍 배열 ) 또는 Array.from(groupedMap.values())(단순 배열 배열) 을 호출 할 수 있습니다 .
  • 매우 유연합니다. 종종,이 맵으로 다음에하려고했던 모든 것을 축소의 일부로 직접 수행 할 수 있습니다.

마지막 요점의 예로, 다음과 같이 id로 (얕은) 병합을 수행하려는 객체 배열이 있다고 상상해보십시오.

const objsToMerge = [{id: 1, name: "Steve"}, {id: 2, name: "Alice"}, {id: 1, age: 20}];
// The following variable should be created automatically
const mergedArray = [{id: 1, name: "Steve", age: 20}, {id: 2, name: "Alice"}]

이를 위해 보통 id로 그룹화 한 다음 각 결과 배열을 병합하여 시작합니다. 대신 다음에서 직접 병합을 수행 할 수 있습니다 reduce().

const mergedArray = Array.from(
    objsToMerge.reduce(
        (entryMap, e) => entryMap.set(e.id, {...entryMap.get(e.id)||{}, ...e}),
        new Map()
    ).values()
);

1
왜 이것이 더 많은 표를 얻지 못하는지 모르겠습니다. 간결하고 읽기 쉬운 (나에게) 외모 효율적입니다. IE11 에서는 비행하지 않지만 개조가 너무 어렵지 않습니다 ( a.reduce(function(em, e){em.set(e.id, (em.get(e.id)||[]).concat([e]));return em;}, new Map()), 대략)
unbob


18

Alasql JavaScript 라이브러리를 사용 하여 수행 할 수 있습니다 .

var data = [ { Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" },
             { Phase: "Phase 1", Step: "Step 1", Task: "Task 2", Value: "10" }];

var res = alasql('SELECT Phase, Step, SUM(CAST([Value] AS INT)) AS [Value] \
                  FROM ? GROUP BY Phase, Step',[data]);

jsFiddle 에서이 예제 시도하십시오 .

BTW : 큰 배열 (100000 레코드 이상) Alasql 더 빠른 tham Linq. jsPref에서 테스트 참조 .

코멘트:

  • VALUE는 SQL의 키워드이므로 여기서는 괄호 안에 Value를 넣습니다.
  • 문자열 값을 숫자 유형으로 변환하려면 CAST () 함수를 사용해야합니다.

18
Array.prototype.groupBy = function(keyFunction) {
    var groups = {};
    this.forEach(function(el) {
        var key = keyFunction(el);
        if (key in groups == false) {
            groups[key] = [];
        }
        groups[key].push(el);
    });
    return Object.keys(groups).map(function(key) {
        return {
            key: key,
            values: groups[key]
        };
    });
};

15

MDN 의 설명서 에는 이 예가Array.reduce() 있습니다.

// Grouping objects by a property
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce#Grouping_objects_by_a_property#Grouping_objects_by_a_property

var people = [
  { name: 'Alice', age: 21 },
  { name: 'Max', age: 20 },
  { name: 'Jane', age: 20 }
];

function groupBy(objectArray, property) {
  return objectArray.reduce(function (acc, obj) {
    var key = obj[property];
    if (!acc[key]) {
      acc[key] = [];
    }
    acc[key].push(obj);
    return acc;
  }, {});
}

var groupedPeople = groupBy(people, 'age');
// groupedPeople is:
// { 
//   20: [
//     { name: 'Max', age: 20 }, 
//     { name: 'Jane', age: 20 }
//   ], 
//   21: [{ name: 'Alice', age: 21 }] 
// }

14

질문에 약간의 답변이 있고 답변이 약간 복잡해 보이지만 중첩 된 (필요한 경우) 그룹별로 바닐라 자바 ​​스크립트를 사용하는 것이 좋습니다 Map.

function groupBy(array, groups, valueKey) {
    var map = new Map;
    groups = [].concat(groups);
    return array.reduce((r, o) => {
        groups.reduce((m, k, i, { length }) => {
            var child;
            if (m.has(o[k])) return m.get(o[k]);
            if (i + 1 === length) {
                child = Object
                    .assign(...groups.map(k => ({ [k]: o[k] })), { [valueKey]: 0 });
                r.push(child);
            } else {
                child = new Map;
            }
            m.set(o[k], child);
            return child;
        }, map)[valueKey] += +o[valueKey];
        return r;
    }, [])
};

var data = [{ 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" }];

console.log(groupBy(data, 'Phase', 'Value'));
console.log(groupBy(data, ['Phase', 'Step'], 'Value'));
.as-console-wrapper { max-height: 100% !important; top: 0; }


9

돌연변이없이 :

const groupBy = (xs, key) => xs.reduce((acc, x) => Object.assign({}, acc, {
  [x[key]]: (acc[x[key]] || []).concat(x)
}), {})

console.log(groupBy(['one', 'two', 'three'], 'length'));
// => {3: ["one", "two"], 5: ["three"]}

8

이 솔루션은 키가 아닌 임의의 함수를 사용하므로 위의 솔루션보다 융통성이 있으며 LINQ 에서 사용되는 람다 식과 비슷한 화살표 함수를 허용합니다 .

Array.prototype.groupBy = function (funcProp) {
    return this.reduce(function (acc, val) {
        (acc[funcProp(val)] = acc[funcProp(val)] || []).push(val);
        return acc;
    }, {});
};

참고 : Array프로토 타입 을 확장 할 것인지는 전적으로 귀하에게 달려 있습니다.

대부분의 브라우저에서 지원되는 예 :

[{a:1,b:"b"},{a:1,c:"c"},{a:2,d:"d"}].groupBy(function(c){return c.a;})

화살표 기능 (ES6)을 사용하는 예 :

[{a:1,b:"b"},{a:1,c:"c"},{a:2,d:"d"}].groupBy(c=>c.a)

위의 두 예제는 다음을 반환합니다.

{
  "1": [{"a": 1, "b": "b"}, {"a": 1, "c": "c"}],
  "2": [{"a": 2, "d": "d"}]
}

나는 ES6 솔루션을 많이 좋아했습니다. Array 프로토 타입을 확장하지 않고 약간의 단순화 :let key = 'myKey'; let newGroupedArray = myArrayOfObjects.reduce(function (acc, val) { (acc[val[key]] = acc[val[key]] || []).push(val); return acc;});
caneta

8

내 접근 방식을 제안하고 싶습니다. 먼저 별도의 그룹화 및 집계 프로토 타입 "그룹화"기능을 선언 할 수 있습니다. 그룹화 할 각 배열 요소에 대해 "해시"문자열을 생성하려면 다른 함수가 필요합니다.

Array.prototype.groupBy = function(hash){
  var _hash = hash ? hash : function(o){return o;};

  var _map = {};
  var put = function(map, key, value){
    if (!map[_hash(key)]) {
        map[_hash(key)] = {};
        map[_hash(key)].group = [];
        map[_hash(key)].key = key;

    }
    map[_hash(key)].group.push(value); 
  }

  this.map(function(obj){
    put(_map, obj, obj);
  });

  return Object.keys(_map).map(function(key){
    return {key: _map[key].key, group: _map[key].group};
  });
}

그룹화가 완료되면 필요한 경우 데이터를 집계 할 수 있습니다

data.groupBy(function(o){return JSON.stringify({a: o.Phase, b: o.Step});})
    /* aggreagating */
    .map(function(el){ 
         var sum = el.group.reduce(
           function(l,c){
             return l + parseInt(c.Value);
           },
           0
         );
         el.key.Value = sum; 
         return el.key;
    });

일반적으로 작동합니다. 크롬 콘솔 에서이 코드를 테스트했습니다. 실수를 개선하고 찾을 수 있습니다.)


감사 ! 접근 방식을 좋아하고 내 요구에 완벽하게 맞습니다 (집계 필요 없음).
aberaud

난 당신이 넣어 당신의 라인을 변경하려는 생각 () : map[_hash(key)].key = key;map[_hash(key)].key = _hash(key);.
Scotty.NET

6

다음과 같은 것이 있다고 상상해보십시오.

[{id:1, cat:'sedan'},{id:2, cat:'sport'},{id:3, cat:'sport'},{id:4, cat:'sedan'}]

이렇게함으로써 : const categories = [...new Set(cars.map((car) => car.cat))]

당신은 이것을 얻을 것입니다 : ['sedan','sport']

설명 : 1. 먼저 배열을 전달하여 새 세트를 작성합니다. 설정은 고유 한 값만 허용하므로 모든 중복 항목이 제거됩니다.

  1. 이제 중복이 사라 졌으므로 스프레드 연산자를 사용하여 다시 배열로 변환합니다 ...

문서 설정 : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set Spread OperatorDoc : https://developer.mozilla.org/en-US/docs/Web/JavaScript / 참조 / 운영자 / 스프레드 _ 구문


나는 당신의 대답을 매우 좋아합니다. 가장 짧은 것입니다. 그러나 여전히 논리를 이해하지 못합니다. 특히 누가 그룹화합니까? 스프레드 연산자 (...)입니까? 또는 'new Set ()'? 우리에게 설명해주십시오 ... 감사합니다
Ivan

1
1. 먼저 배열을 전달하여 새 Set을 만듭니다. 설정은 고유 한 값만 허용하므로 모든 중복 항목이 제거됩니다. 2. 이제 복제본이 사라지고 스프레드 연산자를 사용하여 배열로 다시 변환합니다 ... Doc 설정 : developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… Spread 운영자 : developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Yago Gehres

알았어, 알았어 ... 설명 해주셔서 감사합니다 :)
Ivan

천만에요!
야고 게 레스

6

확인 된 답변-얕은 그룹화. 축소를 이해하는 것이 좋습니다. 질문은 또한 추가 집계 계산의 문제를 제공합니다.

다음은 1) 계산 키 이름과 2) 원하는 키 목록을 제공하고 고유 값을 SQL GROUP과 같은 루트 키로 변환하여 그룹화를 계단식으로 처리하는 완벽한 솔루션입니다. 그렇습니다.

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

함께 플레이 estKey-하나 이상의 필드를 기준으로 그룹화하거나 추가 집계, 계산 또는 기타 처리를 추가 할 수 있습니다.

또한 데이터를 재귀 적으로 그룹화 할 수 있습니다. 예를 들어 처음에는로 그룹화 한 Phase다음 Step필드 별로 그룹화 합니다. 또한 뚱뚱한 휴식 데이터를 날려 버립니다.

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

/**
 * Small helper to get SHALLOW copy of obj WITHOUT prop
 */
const rmProp = (obj, prop) => ( (({[prop]:_, ...rest})=>rest)(obj) )

/**
 * Group Array by key. Root keys of a resulting array is value
 * of specified key.
 *
 * @param      {Array}   src     The source array
 * @param      {String}  key     The by key to group by
 * @return     {Object}          Object with groupped objects as values
 */
const grpBy = (src, key) => src.reduce((a, e) => (
  (a[e[key]] = a[e[key]] || []).push(rmProp(e, key)),  a
), {});

/**
 * Collapse array of object if it consists of only object with single value.
 * Replace it by the rest value.
 */
const blowObj = obj => Array.isArray(obj) && obj.length === 1 && Object.values(obj[0]).length === 1 ? Object.values(obj[0])[0] : obj;

/**
 * Recoursive groupping with list of keys. `keyList` may be an array
 * of key names or comma separated list of key names whom UNIQUE values will
 * becomes the keys of the resulting object.
 */
const grpByReal = function (src, keyList) {
  const [key, ...rest] = Array.isArray(keyList) ? keyList : String(keyList).trim().split(/\s*,\s*/);
  const res = key ? grpBy(src, key) : [...src];
  if (rest.length) {
for (const k in res) {
  res[k] = grpByReal(res[k], rest)
}
  } else {
for (const k in res) {
  res[k] = blowObj(res[k])
}
  }
  return res;
}

console.log( JSON.stringify( grpByReal(inputArray, 'Phase, Step, Task'), null, 2 ) );


5
groupByArray(xs, key) {
    return xs.reduce(function (rv, x) {
        let v = key instanceof Function ? key(x) : x[key];
        let el = rv.find((r) => r && r.key === v);
        if (el) {
            el.values.push(x);
        }
        else {
            rv.push({
                key: v,
                values: [x]
            });
        }
        return rv;
    }, []);
}

이것은 배열을 출력합니다.


4

이전 답변을 기반으로

const groupBy = (prop) => (xs) =>
  xs.reduce((rv, x) =>
    Object.assign(rv, {[x[prop]]: [...(rv[x[prop]] || []), x]}), {});

환경이 지원하는 경우 객체 분산 구문을 살펴 보는 것이 조금 더 좋습니다.

const groupBy = (prop) => (xs) =>
  xs.reduce((acc, x) => ({
    ...acc,
    [ x[ prop ] ]: [...( acc[ x[ prop ] ] || []), x],
  }), {});

여기서 리듀서는 부분적으로 형성된 반환 값 (빈 개체로 시작)을 가져 와서 이전 반환 값의 스프레드 멤버로 구성된 개체와 현재 반복자 값에서 키가 계산 된 새 멤버를 반환합니다. prop해당 값은 현재 값과 함께 해당 소품에 대한 모든 값의 목록입니다.


3

Array.prototype.groupBy = function (groupingKeyFn) {
    if (typeof groupingKeyFn !== 'function') {
        throw new Error("groupBy take a function as only parameter");
    }
    return this.reduce((result, item) => {
        let key = groupingKeyFn(item);
        if (!result[key])
            result[key] = [];
        result[key].push(item);
        return result;
    }, {});
}

var a = [
	{type: "video", name: "a"},
  {type: "image", name: "b"},
  {type: "video", name: "c"},
  {type: "blog", name: "d"},
  {type: "video", name: "e"},
]
console.log(a.groupBy((item) => item.type));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>


3

ES6을 사용하는 불쾌하고 읽기 어려운 솔루션은 다음과 같습니다.

export default (arr, key) => 
  arr.reduce(
    (r, v, _, __, k = v[key]) => ((r[k] || (r[k] = [])).push(v), r),
    {}
  );

이 수행하는 방법을 묻는 분들 일을, 여기에 대한 설명입니다 :

  • 둘 다 =>무료입니다return

  • Array.prototype.reduce기능은 최대 4 개의 매개 변수를 사용합니다. 그렇기 때문에 다섯 번째 매개 변수가 추가되어 기본값을 사용하여 매개 변수 선언 수준에서 그룹 (k)에 대해 저렴한 변수 선언을 가질 수 있습니다. (예, 이것은 마법입니다)

  • 현재 그룹이 이전 반복에 존재하지 않으면 비어있는 새 배열을 만듭니다. ((r[k] || (r[k] = []))이것은 가장 왼쪽의 식, 즉 기존 배열 또는 빈 배열push 로 평가되므로 해당 식 바로 뒤에 이유가 있습니다 어느 쪽이든 배열을 얻습니다.

  • 이 있으면 return쉼표 ,연산자가 가장 왼쪽의 값을 버리고이 시나리오에 대해 이전에 조정 된 그룹을 반환합니다.

동일하게 이해하는 버전은 다음과 같습니다.

export default (array, key) => 
  array.reduce((previous, currentItem) => {
    const group = currentItem[key];
    if (!previous[group]) previous[group] = [];
    previous[group].push(currentItem);
    return previous;
  }, {});

2
이 부분을 조금만 설명해 주
시겠습니까?

@NuwanDammika-둘 다 => 무료 "반환"이 있습니다-감소 기능은 최대 4 개의 매개 변수를 사용합니다. 그렇기 때문에 다섯 번째 매개 변수가 추가되어 그룹 (k)에 대해 저렴한 변수 선언을 가질 수 있습니다. -이전 값에 현재 그룹이없는 경우 비어있는 새 그룹을 만듭니다 ((r [k] || (r [k] = [])). 이것은 가장 왼쪽 표현식, 그렇지 않으면 배열 또는 빈 배열,이 표현식 이후에 즉시 푸시가 발생하는 이유-리턴이있을 때 쉼표 연산자는 가장 왼쪽에있는 값을 버리고 조정 된 이전 그룹을 리턴합니다.
darkndream

2

일반 Array.prototype.groupBy()도구를 생성 할 수 있습니다. 다양성을 위해 재귀 적 접근 방식에서 일부 Haskellesque 패턴 일치를 위해 ES6 fanciness를 사용하십시오. 또한 Array.prototype.groupBy()항목 ( e), 색인 ( i) 및 적용된 배열 ( a)을 인수로 취하는 콜백을 허용 하도록합시다 .

Array.prototype.groupBy = function(cb){
                            return function iterate([x,...xs], i = 0, r = [[],[]]){
                                     cb(x,i,[x,...xs]) ? (r[0].push(x), r)
                                                       : (r[1].push(x), r);
                                     return xs.length ? iterate(xs, ++i, r) : r;
                                   }(this);
                          };

var arr = [0,1,2,3,4,5,6,7,8,9],
    res = arr.groupBy(e => e < 5);
console.log(res);


2

Ceasar의 대답은 좋지만 배열 내부 요소의 내부 속성 (문자열의 경우 길이)에 대해서만 작동합니다.

이 구현은 더 같이 작동 이 링크를

const groupBy = function (arr, f) {
    return arr.reduce((out, val) => {
        let by = typeof f === 'function' ? '' + f(val) : val[f];
        (out[by] = out[by] || []).push(val);
        return out;
    }, {});
};

도움이 되었기를 바랍니다...


2

@mortb, @jmarceli 및 이 게시물 에서

나는 그룹 JSON.stringify()PRIMITIVE VALUE 다중 열에 대한 동일성을 활용합니다 .

타사없이

function groupBy(list, keyGetter) {
    const map = new Map();
    list.forEach((item) => {
        const key = keyGetter(item);
        if (!map.has(key)) {
            map.set(key, [item]);
        } else {
            map.get(key).push(item);
        }
    });
    return map;
}

const pets = [
    {type:"Dog", age: 3, name:"Spot"},
    {type:"Cat", age: 3, name:"Tiger"},
    {type:"Dog", age: 4, name:"Rover"}, 
    {type:"Cat", age: 3, name:"Leo"}
];

const grouped = groupBy(pets,
pet => JSON.stringify({ type: pet.type, age: pet.age }));

console.log(grouped);

Lodash 타사

const pets = [
    {type:"Dog", age: 3, name:"Spot"},
    {type:"Cat", age: 3, name:"Tiger"},
    {type:"Dog", age: 4, name:"Rover"}, 
    {type:"Cat", age: 3, name:"Leo"}
];

let rslt = _.groupBy(pets, pet => JSON.stringify(
 { type: pet.type, age: pet.age }));

console.log(rslt);

keyGetter가 정의되지 않음을 반환 함
Asbar Ali 15:09에

@AsbarAli Chrome 콘솔 버전 66.0.3359.139 (공식 빌드) (64 비트)로 스 니펫을 테스트했습니다. 그리고 모든 것이 잘 작동합니다. 디버깅 중단 점을두고 keyGetter가 정의되지 않은 이유를 확인하십시오. 브라우저 버전 때문일 수 있습니다.
Pranithan T.

2

reduce기능을 지원하는 ES6 기반 버전iteratee .

iteratee기능이 제공되지 않으면 예상대로 작동합니다 .

const data = [{id: 1, score: 2},{id: 1, score: 3},{id: 2, score: 2},{id: 2, score: 4}]

const group = (arr, k) => arr.reduce((r, c) => (r[c[k]] = [...r[c[k]] || [], c], r), {});

const groupBy = (arr, k, fn = () => true) => 
  arr.reduce((r, c) => (fn(c[k]) ? r[c[k]] = [...r[c[k]] || [], c] : null, r), {});

console.log(group(data, 'id'))     // grouping via `reduce`
console.log(groupBy(data, 'id'))   // same result if `fn` is omitted
console.log(groupBy(data, 'score', x => x > 2 )) // group with the iteratee

OP 질문의 맥락에서 :

const data = [ { 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" } ]

const groupBy = (arr, k) => arr.reduce((r, c) => (r[c[k]] = [...r[c[k]] || [], c], r), {});
const groupWith = (arr, k, fn = () => true) => 
  arr.reduce((r, c) => (fn(c[k]) ? r[c[k]] = [...r[c[k]] || [], c] : null, r), {});

console.log(groupBy(data, 'Phase'))
console.log(groupWith(data, 'Value', x => x > 30 ))  // group by `Value` > 30

또 다른 ES6의 그룹화를 반전하고를 사용하는 버전 values으로 keys하고 keys는 AS grouped values:

const data = [{A: "1"}, {B: "10"}, {C: "10"}]

const groupKeys = arr => 
  arr.reduce((r,c) => (Object.keys(c).map(x => r[c[x]] = [...r[c[x]] || [], x]),r),{});

console.log(groupKeys(data))

참고 : 기능은 간결하고 아이디어 만 관련시키기 위해 짧은 형식 (한 줄)으로 게시됩니다. 그것들을 확장하고 추가 오류 검사 등을 추가 할 수 있습니다.


2

나는 당신이 찾고있는 것을 정확하게하는 것처럼 선언적 JS를 검사 groupBy할 것입니다. 또한 :

  • 매우 우수한 성능 (성능 벤치 마크 )
  • 모든 타이핑이 포함되도록 타이프 스크립트로 작성되었습니다.
  • 타사 배열과 유사한 객체를 사용하는 것은 아닙니다.
import { Reducers } from 'declarative-js';
import groupBy = Reducers.groupBy;
import Map = Reducers.Map;

const data = [
    { 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" }
];

data.reduce(groupBy(element=> element.Step), Map());
data.reduce(groupBy('Step'), Map());

1
let groupbyKeys = function(arr, ...keys) {
  let keysFieldName = keys.join();
  return arr.map(ele => {
    let keysField = {};
    keysField[keysFieldName] = keys.reduce((keyValue, key) => {
      return keyValue + ele[key]
    }, "");
    return Object.assign({}, ele, keysField);
  }).reduce((groups, ele) => {
    (groups[ele[keysFieldName]] = groups[ele[keysFieldName]] || [])
      .push([ele].map(e => {
        if (keys.length > 1) {
          delete e[keysFieldName];
        }
        return e;
    })[0]);
    return groups;
  }, {});
};

console.log(groupbyKeys(array, 'Phase'));
console.log(groupbyKeys(array, 'Phase', 'Step'));
console.log(groupbyKeys(array, 'Phase', 'Step', 'Task'));

1

다음은 null 멤버에서 중단되지 않는 ES6 버전입니다.

function groupBy (arr, key) {
  return (arr || []).reduce((acc, x = {}) => ({
    ...acc,
    [x[key]]: [...acc[x[key]] || [], x]
  }), {})
}

1

Scott Sauyet의 답변 에 추가하기 만하면됩니다. 일부 사람들은 하나의 값을 그룹화하는 대신 자신의 기능을 사용하여 value1, value2 등을 그룹화하는 방법에 대한 의견을 묻고있었습니다.

합계 함수를 편집하면됩니다.

DataGrouper.register("sum", function(item) {
    return _.extend({}, item.key,
        {VALUE1: _.reduce(item.vals, function(memo, node) {
        return memo + Number(node.VALUE1);}, 0)},
        {VALUE2: _.reduce(item.vals, function(memo, node) {
        return memo + Number(node.VALUE2);}, 0)}
    );
});

기본 항목 (DataGrouper)을 변경하지 않고 그대로 둡니다.

var DataGrouper = (function() {
    var has = function(obj, target) {
        return _.any(obj, function(value) {
            return _.isEqual(value, target);
        });
    };

    var keys = function(data, names) {
        return _.reduce(data, function(memo, item) {
            var key = _.pick(item, names);
            if (!has(memo, key)) {
                memo.push(key);
            }
            return memo;
        }, []);
    };

    var group = function(data, names) {
        var stems = keys(data, names);
        return _.map(stems, function(stem) {
            return {
                key: stem,
                vals:_.map(_.where(data, stem), function(item) {
                    return _.omit(item, names);
                })
            };
        });
    };

    group.register = function(name, converter) {
        return group[name] = function(data, names) {
            return _.map(group(data, names), converter);
        };
    };

    return group;
}());

1

정렬 기능

export const groupBy = function groupByArray(xs, key, sortKey) {
      return xs.reduce(function(rv, x) {
        let v = key instanceof Function ? key(x) : x[key];
        let el = rv.find(r => r && r.key === v);

        if (el) {
          el.values.push(x);
          el.values.sort(function(a, b) {
            return a[sortKey].toLowerCase().localeCompare(b[sortKey].toLowerCase());
          });
        } else {
          rv.push({ key: v, values: [x] });
        }

        return rv;
      }, []);
    };

견본:

var state = [
    {
      name: "Arkansas",
      population: "2.978M",
      flag:
  "https://upload.wikimedia.org/wikipedia/commons/9/9d/Flag_of_Arkansas.svg",
      category: "city"
    },{
      name: "Crkansas",
      population: "2.978M",
      flag:
        "https://upload.wikimedia.org/wikipedia/commons/9/9d/Flag_of_Arkansas.svg",
      category: "city"
    },
    {
      name: "Balifornia",
      population: "39.14M",
      flag:
        "https://upload.wikimedia.org/wikipedia/commons/0/01/Flag_of_California.svg",
      category: "city"
    },
    {
      name: "Florida",
      population: "20.27M",
      flag:
        "https://upload.wikimedia.org/wikipedia/commons/f/f7/Flag_of_Florida.svg",
      category: "airport"
    },
    {
      name: "Texas",
      population: "27.47M",
      flag:
        "https://upload.wikimedia.org/wikipedia/commons/f/f7/Flag_of_Texas.svg",
      category: "landmark"
    }
  ];
console.log(JSON.stringify(groupBy(state,'category','name')));
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.