Quelle est la méthode la plus efficace pour groupby sur un tableau JavaScript d'objets?

Quelle est la manière la plus efficace de grouper des objets dans un tableau?

par exemple, étant donné ce tableau d'objets:

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

j'affiche cette information dans un tableau. J'aimerais Grouper par différentes méthodes, mais je veux additionner les valeurs.

J'utilise un Underscore.js pour sa fonction groupby, qui est utile, mais ne fait pas tout le tour, parce que je ne veux pas qu'ils "se séparent" mais "fusionnent", plus comme le SQL group by la méthode.

ce que je cherche pourrait totaliser des valeurs spécifiques (si demandé).

donc si je faisais groupby Phase ,je voudrais recevoir:

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

Et si je n'ai groupy Phase / Step , j'aimerais recevoir:

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

y a-t-il un script utile pour cela, ou dois-je m'en tenir à L'utilisation de Underscore.js, et ensuite en boucle à travers l'objet résultant pour faire les totaux moi-même?

234
demandé sur Xufox 2013-01-22 00:18:24

30 réponses

si vous voulez éviter les bibliothèques externes, vous pouvez implémenter de manière concise une version vanille de groupBy() comme suit:

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"]}
355
répondu Ceasar Bautista 2018-09-10 21:56:09

utilisant ES6 objet de la carte:

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

exemple d'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"}]

jsfiddle: https://jsfiddle.net/buko8r5d/

A Propos De La Carte: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map

106
répondu mortb 2017-05-19 12:36:33

bien que la réponse linq soit intéressante, elle est aussi assez lourde. Mon approche est quelque peu différente:

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

, Vous pouvez voir en action sur JSBin .

Je n'ai rien vu dans Underscore qui fait ce que has fait, bien que je puisse le manquer. Il est à peu près le même que _.contains , mais utilise _.isEqual plutôt que === pour les comparaisons. Autre que cela, le reste de ce problème, bien qu'avec une tentative d'être générique.

Maintenant DataGrouper.sum(data, ["Phase"]) renvoie

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

et DataGrouper.sum(data, ["Phase", "Step"]) retourne

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

mais sum n'est qu'une fonction potentielle ici. Vous pouvez enregistrer les autres comme vous voulez:

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

et maintenant DataGrouper.max(data, ["Phase", "Step"]) retournera

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

ou si vous avez enregistré ce:

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

puis appel DataGrouper.tasks(data, ["Phase", "Step"]) vous obtiendrez

[
    {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 lui-même est une fonction. Vous pouvez appeler avec vos données et une liste de propriétés que vous souhaitez regrouper. Il retourne un tableau dont les éléments sont un objet avec deux propriétés: key est la collection de propriétés groupées, vals est un tableau d'objets contenant les propriétés restantes non dans le clé. Par exemple, DataGrouper(data, ["Phase", "Step"]) donnera:

[
    {
        "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 accepte une fonction et crée une nouvelle fonction qui accepte les données initiales et les propriétés de groupe. Cette nouvelle fonction prend alors le format de sortie comme ci-dessus et exécute votre fonction contre chacun d'eux à son tour, retournant un nouveau tableau. La fonction qui est générée est stockée comme une propriété de DataGrouper selon un nom que vous fournissez et aussi retourné si vous voulez juste un local référence.

C'est beaucoup d'explications. Le code est relativement simple, je l'espère!

52
répondu Scott Sauyet 2017-12-22 12:41:40

c'est probablement plus facile à faire avec linq.js , qui est destiné à être une véritable mise en œuvre de LINQ en JavaScript ( Démo ):

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

résultat:

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

ou, plus simplement, en utilisant les sélecteurs basés sur les chaînes de caractères ( Démo ):

linq.GroupBy("$.Phase", "",
    "k,e => { Phase:k, Value:e.Sum('$.Value|0') }").ToArray();
35
répondu mellamokb 2013-01-21 20:48:17

avec ES6:

const groupBy = (items, key) => items.reduce(
  (result, item) => ({
    ...result,
    [item[key]]: [
      ...(result[item[key]] || []),
      item,
    ],
  }), 
  {},
);
30
répondu Joseph Nields 2017-11-02 21:31:03

je voudrais vérifier lodash groupe il semble faire exactement ce que vous cherchez. Il est également très léger et très simple.

exemple de violon: https://jsfiddle.net/r7szvt5k /

pourvu que votre nom de tableau soit arr le groupBy avec lodash est juste:

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
24
répondu jmarceli 2018-08-22 10:58:16
_.groupBy([{tipo: 'A' },{tipo: 'A'}, {tipo: 'B'}], 'tipo');
>> Object {A: Array[2], B: Array[1]}

de: http://underscorejs.org/#groupBy

17
répondu Julio Marins 2014-11-12 13:54:30

vous pouvez le faire avec Alasql bibliothèque 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]);

Essayez cet exemple à jsFiddle .

BTW: Sur de grands tableaux (100000 enregistrements et de plus en plus) Alasql plus rapide tham Linq. Voir le test à jsPref .

commentaires:

  • ici je mets la valeur en carré crochets, parce que la valeur est un mot-clé dans SQL
  • je dois utiliser la fonction CAST() pour convertir des valeurs de chaîne en type de nombre.
13
répondu agershun 2014-12-23 09:09:46
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]
        };
    });
};
13
répondu cezarypiatek 2015-06-26 20:15:10

vous pouvez construire un ES6 Map à partir de array.reduce() .

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

cela présente quelques avantages par rapport aux autres solutions:

  • il ne nécessite pas de bibliothèques (contrairement par exemple _.groupBy() )
  • vous obtenez un JavaScript Map plutôt qu'un objet (par exemple retourné par _.groupBy() ). Cela a beaucoup d'avantages , y compris:
    • il se souvient de l'ordre dans lequel les articles ont été ajoutés pour la première fois,
    • les clés peuvent être n'importe quel type plutôt que seulement des chaînes.
  • Un Map est plus utile pour résultat qu'un tableau de tableaux. Mais si vous voulez un tableau de tableaux, vous pouvez ensuite appeler Array.from(groupedMap.entries()) (pour un tableau de [key, group array] paires) ou Array.from(groupedMap.values()) (pour un simple tableau de tableaux).
  • c'est assez flexible; souvent, quoi que vous planifiez de faire ensuite avec cette carte peut être fait directement dans le cadre de la réduction.

comme exemple du dernier point, imaginez que j'ai un tableau d'objets que je veux faire une fusion (superficielle) par id, comme ceci:

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

pour ce faire, je commencerais habituellement par Grouper par id, puis fusionner chacun des tableaux résultants. Au lieu de cela, vous pouvez faire la fusion directement dans le reduce() :

const mergedArray = Array.from(
    objsToMerge.reduce(
        (entryMap, e) => entryMap.set(e.id, {...entryMap.get(e.id)||{}, ...e}),
        new Map()
    ).values()
);
8
répondu Arthur Tacca 2017-12-21 19:13:51

bien que la question ait des réponses et que les réponses semblent un peu trop compliquées, je suggère d'utiliser le Javascript de la vanille pour group-by.

cette solution dispose d'une fonction qui prend un tableau avec les données array et un nom de propriété pour le retour col et un nom de propriété pour compter une valeur value .

, cette fonction s'appuie sur un objet, qui agit comme une table de hachage pour le résultat.

function groupBy(array, col, value) {
    var r = [], o = {};
    array.forEach(function (a) {
        if (!o[a[col]]) {
            o[a[col]] = {};
            o[a[col]][col] = a[col];
            o[a[col]][value] = 0;
            r.push(o[a[col]]);
        }
        o[a[col]][value] += +a[value];
    });
    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" }];

document.write('<pre>' + JSON.stringify(groupBy(data, 'Phase', 'Value'), 0, 4) + '</pre>');
6
répondu Nina Scholz 2016-03-13 12:38:15

sans mutations:

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"]}
6
répondu Bless 2017-07-13 20:44:54

j'aimerais suggérer mon approche. Tout d'abord, regrouper et regrouper séparément. Permet de déclarer prototypique "groupe" de la fonction. Il faut une autre fonction pour produire une chaîne de "hachage" pour chaque élément du tableau à grouper.

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

quand le regroupement est fait, vous pouvez agréger des données comme vous avez besoin, dans votre cas

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

en commun ça marche. j'ai testé ce code dans la console chrome. et n'hésitez pas à progresser et à trouver des erreurs ;)

6
répondu Anton 2017-11-29 08:49:32

cette solution prend n'importe quelle fonction arbitraire (pas une clé) de sorte qu'il est plus flexible que les solutions ci-dessus, et permet fonctions Flèche 151980920" ", qui sont similaires à lambda expressions utilisé dans LINQ :

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

NOTE: c'est à vous de décider si vous souhaitez étendre le prototype de Array .

exemple pris en charge dans la plupart des navigateurs:

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

exemple utilisant des fonctions de flèche (ES6):

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

deux exemples ci-dessus retour:

{
  "1": [{"a": 1, "b": "b"}, {"a": 1, "c": "c"}],
  "2": [{"a": 2, "d": "d"}]
}
4
répondu Diego 2017-04-19 21:01:23

basé sur les réponses précédentes

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

et c'est un peu plus agréable à regarder avec la syntaxe de propagation d'objet, si votre environnement prend en charge.

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

ici, notre réducteur prend la valeur de retour partiellement formée (à partir d'un objet vide), et renvoie un objet composé des membres étalés de la valeur de retour précédente, avec un nouveau membre dont la clé est calculée à partir de la valeur actuelle de l'itéré à prop et dont la valeur est une liste de toutes les valeurs de cette prop avec la valeur actuelle.

3
répondu Benny Powers 2018-03-12 12:21:12

permet de générer un outil générique Array.prototype.groupBy() . Juste pour la variété employons ES6 fanciness l'opérateur de propagation pour un certain motif haskellesque appariement sur une approche récursive. Faisons aussi notre Array.prototype.groupBy() pour accepter un callback qui prend l'élément ( e ) l'index ( i ) et le tableau appliqué ( a ) comme arguments.

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
répondu Redu 2017-07-05 20:25:14

la réponse de Ceasar est bonne, mais ne fonctionne que pour les propriétés internes des éléments à l'intérieur du tableau (longueur en cas de chaîne).

cette implémentation fonctionne plus comme: ce lien

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

espérons que cette aide...

2
répondu Roey 2018-01-08 14:30:57

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>
2
répondu Jean-Philippe Ménard 2018-04-14 18:39:16

MDN a cet exemple dans sa documentation 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 }] 
// }
2
répondu HoppyKamper 2018-09-13 15:14:09
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;
    }, []);
}

ce tableau de sortie.

1
répondu tomitrescak 2016-08-03 10:55:18
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
répondu Tom Jiang 2017-05-02 22:47:09

Voici une version ES6 qui ne cassera pas sur les membres nuls

function groupBy (arr, key) {
  return (arr || []).reduce((acc, x = {}) => ({
    ...acc,
    [x[key]]: [...acc[x[key]] || [], x]
  }), {})
}
1
répondu bigkahunaburger 2017-10-28 22:26:57

avec fonction de tri

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

échantillon:

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')));
1
répondu amorenew 2018-03-05 07:47:03

De @mortb, @jmarceli réponse et de ce post ,

je prends le parti de JSON.stringify() à l'identité de la VALEUR PRIMITIVE plusieurs colonnes de groupe.

sans tiers

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

avec Lodash tiers

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);
1
répondu Pranithan T. 2018-03-23 10:56:41

j'ai emprunté cette méthode à underscore.js fiddler

window.helpers=(function (){
    var lookupIterator = function(value) {
        if (value == null){
            return function(value) {
                return value;
            };
        }
        if (typeof value === 'function'){
                return value;
        }
        return function(obj) {
            return obj[value];
        };
    },
    each = function(obj, iterator, context) {
        var breaker = {};
        if (obj == null) return obj;
        if (Array.prototype.forEach && obj.forEach === Array.prototype.forEach) {
            obj.forEach(iterator, context);
        } else if (obj.length === +obj.length) {
            for (var i = 0, length = obj.length; i < length; i++) {
                if (iterator.call(context, obj[i], i, obj) === breaker) return;
            }
        } else {
            var keys = []
            for (var key in obj) if (Object.prototype.hasOwnProperty.call(obj, key)) keys.push(key)
            for (var i = 0, length = keys.length; i < length; i++) {
                if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return;
            }
        }
        return obj;
    },
    // An internal function used for aggregate "group by" operations.
    group = function(behavior) {
        return function(obj, iterator, context) {
            var result = {};
            iterator = lookupIterator(iterator);
            each(obj, function(value, index) {
                var key = iterator.call(context, value, index, obj);
                behavior(result, key, value);
            });
            return result;
        };
    };

    return {
      groupBy : group(function(result, key, value) {
        Object.prototype.hasOwnProperty.call(result, key) ? result[key].push(value) :              result[key] = [value];
        })
    };
})();

var arr=[{a:1,b:2},{a:1,b:3},{a:1,b:1},{a:1,b:2},{a:1,b:3}];
 console.dir(helpers.groupBy(arr,"b"));
 console.dir(helpers.groupBy(arr,function (el){
   return el.b>2;
 }));
0
répondu Roman Yudin 2014-03-25 11:24:42

vous pouvez le faire de la façon suivante. Je viens de former un nouveau tableau et je l'ai retourné de la fonction groupBy . Compte calculé à partir de la boucle juste par .carte fonction

var arr = [ 
        { 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 groupBy = (arr, pahse, step='') => {

   var pahseArr = [];
   var resultArr = [];

   arr.map((item)=>{
     var pushed = false;
     pahseArr.map((ele)=>{
       if(ele===item.Phase){
         pushed = true;
       }
     })
     if(!pushed){
       pahseArr.push(item.Phase);
     }     
   })

   pahseArr.map((item)=>{
      var sum = 0;
      arr.map((ele)=>{
        if(ele.Phase===item){
          sum += parseFloat(ele.Value)
        }
      })
      resultArr.push({
        Phase: item,
        Value: sum
      })
   })

   if(step!=''){
     var resultArr = [];


     pahseArr.map((item)=>{
         var stepArr = [];

         arr.map((item2)=>{
           var pushed = false;
           stepArr.map((ele)=>{
             if(ele===item2.Step){
               pushed = true;
             }
           })
           if(!pushed){
             stepArr.push(item2.Step);
           } 
         })

         stepArr.map((item1)=>{
            var sum = 0;
            arr.map((ele)=>{
              if(ele.Step===item1 && ele.Phase===item){
                sum += parseFloat(ele.Value)
              }
            })
            resultArr.push({
              Phase: item,
              Step: item1,
              Value: sum
            })
         })

     })
     return resultArr;
   }   
   return resultArr;

}

console.log(groupBy(arr, 'Phase'));
console.log(groupBy(arr, 'Phase', 'Step'));
0
répondu Vishal Raut 2018-02-01 09:35:15

j'ai développé la réponse acceptée pour inclure le groupement par propriétés multiples, Ajouter thenby et le rendre purement fonctionnel sans mutation. Voir la démo à https://stackblitz.com/edit/typescript-ezydzv

export interface Group {
  key: any;
  items: any[];
}

export interface GroupBy {
  keys: string[];
  thenby?: GroupBy;
}

export const groupBy = (array: any[], grouping: GroupBy): Group[] => {
  const keys = grouping.keys;
  const groups = array.reduce((groups, item) => {
    const group = groups.find(g => keys.every(key => item[key] === g.key[key]));
    const data = Object.getOwnPropertyNames(item)
      .filter(prop => !keys.find(key => key === prop))
      .reduce((o, key) => ({ ...o, [key]: item[key] }), {});
    return group
      ? groups.map(g => (g === group ? { ...g, items: [...g.items, data] } : g))
      : [
          ...groups,
          {
            key: keys.reduce((o, key) => ({ ...o, [key]: item[key] }), {}),
            items: [data]
          }
        ];
  }, []);
  return grouping.thenby ? groups.map(g => ({ ...g, items: groupBy(g.items, grouping.thenby) })) : groups;
};
0
répondu Adrian Brand 2018-07-10 04:54:47
data = [{id:1, name:'BMW'}, {id:2, name:'AN'}, {id:3, name:'BMW'}, {id:1, name:'NNN'}]
key = 'id'//try by id or name
data.reduce((previous, current)=>{
    previous[current[key]] && previous[current[key]].length != 0 ? previous[current[key]].push(current) : previous[current[key]] = new Array(current)
    return previous;
}, {})
0
répondu Landaida 2018-07-19 19:21:12

basé sur l'idée originale de @Ceasar Bautista , j'ai modifié le code et créé une fonction groupBy en tapant.

static groupBy(data: any[], comparator: (v1: any, v2: any) => boolean, onDublicate: (uniqueRow: any, dublicateRow: any) => void) {
    return data.reduce(function (reducedRows, currentlyReducedRow) {
      let processedRow = reducedRows.find(searchedRow => comparator(searchedRow, currentlyReducedRow));

      if (processedRow) {
        // currentlyReducedRow is a dublicateRow when processedRow is not null.
        onDublicate(processedRow, currentlyReducedRow)
      } else {
        // currentlyReducedRow is unique and must be pushed in the reducedRows collection.
        reducedRows.push(currentlyReducedRow);
      }

      return reducedRows;
    }, []);
  };

cette fonction accepte un callback (comparateur) qui compare les lignes et trouve les dublicates et un second callback (ondulicate) qui agrège les dublicates.

exemple d'utilisation:

data = [
    { name: 'a', value: 10 },
    { name: 'a', value: 11 },
    { name: 'a', value: 12 },
    { name: 'b', value: 20 },
    { name: 'b', value: 1 }
  ]

  private static demoComparator = (v1: any, v2: any) => {
    return v1['name'] === v2['name'];
  }

  private static demoOnDublicate = (uniqueRow, dublicateRow) => {
    uniqueRow['value'] += dublicateRow['value'];    
  };

appel

groupBy(data, demoComparator, demoOnDublicate) 

va effectuer un groupe par qui calcule la somme de valeur.

{name: "a", value: 33}
{name: "b", value: 21}

nous pouvons créer autant de ces fonctions de rappel que requis par le projet et agréger les valeurs si nécessaire. Dans un cas, par exemple, j'ai besoin de fusionner deux tableaux au lieu d'additionner les données.

0
répondu Panagiotis Piperopoulos 2018-08-01 08:24:12