For trickier collections, though, you’ll need to pass in a function to identify exactly what you want the maximum or minimum of: var words = ["flowers", "the", "hate", "moribund", "sesqu
Trang 1Using Enumerable#select
What if we need to find several needles in a haystack?
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].select(isEven);
//-> [2, 4, 6, 8, 10]
Just like detect,selecttests each item against the given function But it doesn’t stop
after the first match—it will return all items in the collection that match the criteria
["foo", 1, "bar", "baz", 2, null].select( function(item) {
return typeof item === "string";
});
//-> ["foo", "bar", "baz"]
Unlike detect, which is guaranteed to return only one item, selectwill always return
an array If there are no matches, it will return an empty array
Using Enumerable#reject
Nearly identical to select,rejectwill return all the items that fail a particular test.
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].reject(isEven);
//-> [1, 3, 5, 7, 9]
Using Enumerable#partition
When you need to separate a collection into two groups, use Enumerable#partition It returns a two-item array: the first an array of all items that passed the test, and the sec-ond an array of all items that failed the test
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].partition(isEven);
//-> [ [2, 4, 6, 8, 10], [1, 3, 5, 7, 9] ]
Sorting Collections: min, max, and sortBy
The next three Enumerablemethods—min,max, and sortBy—address common situations of arranging collections by value
Trang 2Using Enumerable#min and #max
Much like Math.minand Math.max, which identify the smallest and largest values of all the
arguments passed to them, Enumerable#minand #maxwill find the smallest and largest
val-ues in an existing group:
Math.min(1, 4, 9, 16, 25); //-> 1
Math.max(1, 4, 9, 16, 25); //-> 25
var squares = [1, 4, 9, 16, 25];
squares.min(); //-> 1
squares.max(); //-> 25
In this example, it’s easy to figure out what the minimum and maximum values
are—numbers are directly comparable For trickier collections, though, you’ll need to
pass in a function to identify exactly what you want the maximum or minimum of:
var words = ["flowers", "the", "hate", "moribund", "sesquicentennial"];
words.max( function(word) { return word.length; } ); //-> 16
words.min( function(word) { return word.length; } ); //-> 3
Comparing on string length, we get 3and 16as the min and max, respectively—the
lengths of the shortest and longest words in the array
Using Enumerable#sortBy
JavaScript has a built-in sorting method: Array#sort Why do we need another?
Let’s illustrate If we try to use Array#sorton an example set of numbers, we’ll be
in for a surprise:
[2, 5, 4, 8, 9, 1, 3, 10, 7, 6].sort();
//-> [1, 10, 2, 3, 4, 5, 6, 7, 8, 9]
As it happens, sortcalled with no arguments will coerce the array items into strings
before it compares them (10is greater than 2, but "10"is less than "2".) If we want to
compare the numbers directly, we must pass a function argument into sort:
[2, 5, 4, 8, 9, 1, 3, 10, 7, 6].sort(function(a, b) {
return a – b;
});
//-> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Trang 3The passed function tells sorthow to compare any two items in the array In the pre-ceding example, if ais greater than b, then the return value will be positive, indicating that ashould followb If bis greater than a, then the return value will be negative, and a
will precedeb (If the return value is 0, then the two are equal, of course.)
This is nuts—or, at the very least, surprising We need a better sort function
Enumerable#sortByworks a little differently It, too, takes a function argument, but the function is used only to translate a given item to a comparison value:
var words = ["aqueous", "strength", "hated", "sesquicentennial", "area"];
// sort by word length
words.sortBy( function(word) { return word.length; } );
//-> ["area", "hated", "aqueous", "strength", "sesquicentennial"]
// sort by number of vowels in the word
words.sortBy( function(word) { return word.match(/[aeiou]/g).length; } )
//-> ["strength", "hated", "area", "aqueous", "sesquicentennial"]
As you can see, the comparison function takes one argument, rather than two Most developers will find this far more intuitive
Advanced Enumeration: map, inject, invoke,
and pluck
The next four Enumerablemethods carry more cryptic names, but are every bit as useful
as the methods described previously
Using Enumerable#map and Enumerable#inject
The mapmethod performs parallel transformation It applies a function to every item in
a collection, pushes each result into an array, and then returns that array
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map( function(num) { return num * num; } );
//-> [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
The injectmethod returns an accumulated collection Think of it as a hat being passed around to each of the items—each item throws a different quantity into the hat
Trang 4var words = ["aqueous", "strength", "hated", "sesquicentennial", "area"];
var totalLength = words.inject(0, function(memo, string) {
return memo + string.length;
});
// -> 40
Since we need to keep track of memo—the variable that stores our running total—the
arguments for injectare slightly different The first argument of injectis our starting
value—0, in this case, since we’re dealing with a numeric property The second argument
is the function we’re using against the collection’s items
This inner function itself takes two arguments: our running total (memo) and the item
we’re working with (string) The function’s return value will be used as the memo for the
next item in the collection
This can be a bit confusing, so let’s add a logging statement to the inner function to
illustrate what’s going on:
var totalLength = words.inject(0, function(memo, string) {
console.log('received ' + memo + '; added ' + string.length);
console.log('returning ' + (memo + string.length));
return memo + string.length;
});
//-> received 0; added 7
//-> returning 7
//-> received 7; added 8
//-> returning 15
//-> received 15; added 5
//-> returning 20
//-> received 20; added 16
//-> returning 36
//-> received 36; added 4
//-> returning 40
//-> 40
Now that you can see each step in the enumeration, the behavior of injectshould be
easier to follow
Trang 5Using Enumerable#pluck and Enumerable#invoke
These two Enumerablemethods are somewhat special They take a string as their first argument instead of a function
The pluckmethod collects individual properties on each of the objects on the collection:
var words = ["aqueous", "strength", "hated", "sesquicentennial", "area"];
words.pluck('length');
//-> [7, 8, 5, 16, 4]
Note that this example code is equivalent to
words.map( function(word) { return word.length; } );
but is shorter and more meaningful
The invokemethod is similar: it calls the specified instance method on each item Let’s illustrate by using one of Prototype’s string methods:
" aqueous ".strip(); //-> "aqueous"
var paddedWords = [" aqueous ", "strength ", " hated ",
"sesquicencennial", " area "];
words.invoke('strip');
//-> ["aqueous", "strength", "hated", "sesquicentennial", "area"]
This code is equivalent to
words.map( function(word) { return word.strip(); } );
but invokecan also pass arguments along to the instance method Simply add the
required number of arguments after the first:
"swim/swam".split('/'); //-> ["swim", "swam"]
"swim/swam".replace('/', '|'); //-> "swim|swam"
var wordPairs = ["swim/swam", "win/lose", "glossy/matte"];
wordPairs.invoke('split', '/');
//-> [ ["swim", "swam"], ["win", "lose"], ["glossy", "matte"] ]
wordPairs.invoke('replace', '/', '|');
//-> ["swim|swam", "win|lose", "glossy|matte"]
The map,inject,pluck, and invokemethods greatly simplify four very common code patterns Become familiar with them and you’ll start to notice uses for them all over the code you write
Trang 6Other Collections That Use Enumerable
Two other Prototype classes that make use of Enumerableare Hashand ObjectRange
Together they serve as great examples of how to use Enumerablewith other types of
collections
Hash
There is no built-in facility in JavaScript for setting key/value pairs—the construct that’s
known as a hash (in Ruby), a dictionary (in Python), or an associative array (in PHP).
There is, of course, an ordinary object, and this suffices for most cases
var airportCodes = {
AUS: "Austin-Bergstrom Int'l",
HOU: "Houston/Hobby",
IAH: "Houston/Intercontinental",
DAL: "Dallas/Love Field",
DFW: "Dallas/Fort Worth"
};
for (var key in airportCodes) {
console.log(key + " is the airport code for " + airportCodes[key] + '.');
}
>>> AUS is the airport code for Austin-Bergstrom Int'l
>>> HOU is the airport code for Houston/Hobby
>>> IAH is the airport code for Houston/Intercontinental
>>> DAL is the airport code for Dallas/Love Field
>>> DFW is the airport code for Dallas/Fort Worth
We can declare an object and iterate over its properties quite easily This doesn’t get
us everything a hash would, but it comes very close
Eventually, however, we’ll run into two major problems
Objects Have No Key Safety
An object is not a blank slate when it’s declared It has native properties and methods
with names that may conflict with the names you’d want to use for your keys