开发者

What's a good JavaScript pattern for categorizing things into types?

I'm looking for a way (in JavaScript) to collect a set of objects into multiple arrays, where each array contains a certain type of object, and the arrays are stored as values in an associative array, with the keys being the types. For example:

Input:

[<apple>, <cat>, <pear>, <mercedes>, <dog>, <ford>, <orange>]

Output:

{
  'fruit': [<apple>, <pear>, <orange>],
  'animal': [<cat>, <dog>],
  'car': [<mercedes>, <ford>]
}

In ruby, you could do the following:

things_by_type = {}
things.each do |thing|
  (things_by_type[thing.type] ||= []) << thing
end

which is nice and concise.

What's a good pattern for doing the same thing in JavaScript that's concise and efficient? I could do something like this, but it's not as nice:

var thing, things_by_type = {};
for (var i = 0; i <开发者_如何学编程 things.length; i++) {
  thing = things[i];
  if(things_by_type[thing.type]) {
    things_by_type[thing.type].push(thing);
  } else {
    things_by_type[thing.type] = [thing];
  }
}


I'm not sure if it's a good pattern, but it's similar to your ruby sample:

var things_by_type = {};
for (var i in things) {
  var thing = things[i];
  (things_by_type[thing.type] || (things_by_type[thing.type] = [])).push(thing);
}

And if you can assume Javascript 1.6:

var things_by_type = {};
things.forEach(function(thing) {
  (things_by_type[thing.type] || (things_by_type[thing.type] = [])).push(thing);
})


In ruby, you could do the following:

things_by_type = {}
things.each do |thing|
  (things_by_type[thing.type] ||= []) << thing
end

which is nice and concise.

Actually, you can make that even nicer.

First off, Hash.new takes a block argument which will be called every time a non-existing key is referenced. You can use that to create that key. That way you get rid of the conditional logic inside the block.

things_by_type = Hash.new {|h, k| h[k] = [] }
things.each do |thing|
  things_by_type[thing.type] << thing
end

Secondly, what you have here is called a fold or reduce: you are "folding" or "reducing" a collection (the array of objects) into a single value (the hash, which confusingly also happens to be a collection, but is nonetheless a single value).

You can generally easily spot this pattern by looking for places where you initialize some variable, then loop over a collection and manipulate that variable at every iteration of the loop.

Ruby has folding built in, via the Enumerable#reduce method:

things.reduce(Hash.new {|h, k| h[k] = [] }) do |h, thing|
  h.tap { h[thing.type] << thing }
end

But what you are really doing, is grouping the array by the type attribute of its elements, which is also built into Ruby as Enumerable#group_by:

things.group_by {|thing| thing.type }

Which can be further simplified by using Symbol#to_proc to

things.group_by(&:type)

Unfortunately, ECMAScript doesn't have groupBy, nor default values for non-existing properties, but it does have Array.prototype.reduce:

things.reduce(function (acc, thing) {
    (acc[thing.type] || (acc[thing.type] = [thing])).push(thing);
    return acc;
}, {});


almost the same code, but works a bit different, you can use the fancy set function easier and it separates logic:

var a = {set:function(type,thing){
  if (this[type]) {
    this[type].push(thing);
  } else {
    this[type] = [thing];
  }
}};

a.set('a',0);
a.set('b',1);
a.set('a',2);
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜