web-dev-qa-db-fra.com

Underscore.js groupPar plusieurs valeurs

À l’aide de Underscore.js, j’essaie de regrouper une liste d’éléments plusieurs fois, c.-à-d.

Groupe par taille puis pour chaque taille, groupe par catégorie ...

http://jsfiddle.net/rickysullivan/WTtXP/1/

Idéalement, j'aimerais avoir une fonction ou une extension _.groupBy() afin que vous puissiez lancer un tableau avec les paramètres à regrouper.

var multiGroup = ['size', 'category'];

Probablement pourrait juste faire un mixin ...

_.mixin({
    groupByMulti: function(obj, val, arr) {
        var result = {};
        var iterator = typeof val == 'function' ? val : function(obj) {
                return obj[val];
            };
        _.each(arr, function(arrvalue, arrIndex) {
            _.each(obj, function(value, objIndex) {
                var key = iterator(value, objIndex);
                var arrresults = obj[objIndex][arrvalue];
                if (_.has(value, arrvalue))
                    (result[arrIndex] || (result[arrIndex] = [])).Push(value);

J'ai mal à la tête, mais je pense qu'il faut pousser plus loin ici ...

            });
        })
        return result;
    }
});

properties = _.groupByMulti(properties, function(item) {

    var testVal = item["size"];

    if (parseFloat(testVal)) {
        testVal = parseFloat(item["size"])
    }

    return testVal

}, multiGroup);
19
rickysullivan

Une implémentation récursive simple:

_.mixin({
  /*
   * @mixin
   *
   * Splits a collection into sets, grouped by the result of running each value
   * through iteratee. If iteratee is a string instead of a function, groups by
   * the property named by iteratee on each of the values.
   *
   * @param {array|object} list - The collection to iterate over.
   * @param {(string|function)[]} values - The iteratees to transform keys.
   * @param {object=} context - The values are bound to the context object.
   * 
   * @returns {Object} - Returns the composed aggregate object.
   */
  groupByMulti: function(list, values, context) {
    if (!values.length) {
      return list;
    }
    var byFirst = _.groupBy(list, values[0], context),
        rest    = values.slice(1);
    for (var prop in byFirst) {
      byFirst[prop] = _.groupByMulti(byFirst[prop], rest, context);
    }
    return byFirst;
  }
});

Démo dans votre jsfiddle

39
Bergi

Je pense que la réponse de @ Bergi peut être un peu simplifiée en utilisant la variable mapValues de Lo-Dash (pour mapper des fonctions sur des valeurs d'objet). Cela nous permet de regrouper les entrées dans un tableau par plusieurs clés de manière imbriquée:

_ = require('lodash');

var _.nest = function (collection, keys) {
  if (!keys.length) {
    return collection;
  }
  else {
    return _(collection).groupBy(keys[0]).mapValues(function(values) { 
      return nest(values, keys.slice(1));
    }).value();
  }
};

J'ai renommé la méthode en nest car elle finit par jouer le même rôle que celui joué par l'opérateur nest de D3. Voir this Gist pour plus de détails et this fiddle pour une utilisation démontrée avec votre exemple.

lodashnidgroupby

16
joyrexus

Que diriez-vous de ce hack plutôt simple?

console.log(_.groupBy(getProperties(), function(record){
    return (record.size+record.category);
}));
13
Pankaj Sharma

Découvrez cette extension de soulignement: Underscore.Nest , par Irene Ros. 

La sortie de cette extension sera légèrement différente de ce que vous spécifiez, mais le module ne contient qu'environ 100 lignes de code. Vous devriez donc pouvoir numériser pour obtenir une direction. 

1
Jacob Brown

Un exemple avec lodash et mixin

_.mixin({
'groupByMulti': function (collection, keys) {
if (!keys.length) {
 return collection;
 } else {
  return _.mapValues(_.groupBy(collection,_.first(keys)),function(values) {
    return _.groupByMulti(values, _.rest(keys));
  });
}
}
});    
0
Marcel Santos

Les améliorations apportées par joyrexus à la méthode de bergi ne tirent pas parti du système de soulignement/lodash. La voici en mixin:

_.mixin({
  nest: function (collection, keys) {
    if (!keys.length) {
      return collection;
    } else {
      return _(collection).groupBy(keys[0]).mapValues(function(values) {
        return _.nest(values, keys.slice(1));
      }).value();
    }
  }
});
0
Vedant Misra

C'est un excellent cas d'utilisation pour le réduire phase de map-réduire . Elle ne sera pas aussi élégante sur le plan visuel que la fonction multi-groupe (vous ne pouvez pas simplement transmettre un tableau de clés sur lequel grouper), mais globalement, ce modèle vous donne plus de flexibilité pour transformer vos données. EXEMPLE

var grouped = _.reduce(
    properties, 
    function(buckets, property) {
        // Find the correct bucket for the property
        var bucket = _.findWhere(buckets, {size: property.size, category: property.category});

        // Create a new bucket if needed.
        if (!bucket) {
            bucket = {
                size: property.size, 
                category: property.category, 
                items: []
            };
            buckets.Push(bucket);
        }

        // Add the property to the correct bucket
        bucket.items.Push(property);
        return buckets;
    }, 
    [] // The starting buckets
);

console.log(grouped)

Mais si vous le voulez juste dans un mix de soulignement, voici ce que je peux faire:

_.mixin({
'groupAndSort': function (items, sortList) {
    var grouped = _.reduce(
        items,
        function (buckets, item) {
            var searchCriteria = {};
            _.each(sortList, function (searchProperty) { searchCriteria[searchProperty] = item[searchProperty]; });
            var bucket = _.findWhere(buckets, searchCriteria);

            if (!bucket) {
                bucket = {};
                _.each(sortList, function (property) { bucket[property] = item[property]; });
                bucket._items = [];
                buckets.Push(bucket);
            }

            bucket._items.Push(item);
            return buckets;
        },
        [] // Initial buckets
    );

    grouped.sort(function (x, y) {
        for (var i in sortList) {
            var property = sortList[i];
            if (x[property] != y[property])
                return x[property] > y[property] ? 1 : -1;
        }
        return 0;
    });

    return _.map(grouped, function (group) {
        var toReturn = { key: {}, value: group.__items };
        _.each(sortList, function (searchProperty) { toReturn.key[searchProperty] = group[searchProperty]; });
        return toReturn;
    });
});
0
Tyler

Voici une fonction facile à comprendre.

function mixin(list, properties){

    function grouper(i, list){

        if(i < properties.length){
            var group = _.groupBy(list, function(item){
                var value = item[properties[i]];
                delete item[properties[i]];
                return value;
            });

            _.keys(group).forEach(function(key){
                group[key] = grouper(i+1, group[key]);
            });
            return group;
        }else{
            return list;
        }
    }

    return grouper(0, list);

}
0
Kashif Nazar

Le regroupement par clé composite a tendance à mieux fonctionner dans la plupart des situations

const groups = _.groupByComposite(myList, ['size', 'category']);

Démo utilisant le violon de OP

Mixin

_.mixin({
  /*
   * @groupByComposite
   *
   * Groups an array of objects by multiple properties. Uses _.groupBy under the covers,
   * to group by a composite key, generated from the list of provided keys.
   *
   * @param {Object[]} collection - the array of objects.
   * @param {string[]} keys - one or more property names to group by.
   * @param {string} [delimiter=-] - a delimiter used in the creation of the composite key.
   *
   * @returns {Object} - the composed aggregate object.
   */
  groupByComposite: (collection, keys, delimiter = '-') =>
    _.groupBy(collection, (item) => {
      const compositeKey = [];
      _.each(keys, key => compositeKey.Push(item[key]));
      return compositeKey.join(delimiter);
    }),
});
0
2Toad