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);
精彩评论