JavaScript
Underscore.js

遅すぎたUnderscore.js詳述 - Collections編

More than 3 years have passed since last update.

いまどこ?


基本の関数をひとつずつ見ていく章です

1 . 遅すぎたUnderscore.js入門 - 全体像

2 . 遅すぎたUnderscore.js詳述 - Collections編 ←いまここ

3 . 遅すぎたUnderscore.js入門 - Arrays編

4 . Functions

5 . Objects

6 . Utility

7 . Chaining

前回でUnderscore.jsの全体像を見終わったので、今回からひとつずつAPIをみていきます。

基本的に本家サイト(underscorejs.org)の章立てに合わせていきたいと思います。

この章は最初が重たいですが、だんだんと被ってくるので最初を抜けたら楽になります。


注意点

本家のコードを加筆・書き換えしたり、メタファー(たとえ)としてあえて厳密でない言葉で書いたりしていますのでご了承願います。

また、重複する説明が削られていくので、上から読んでいくことを推奨します。

間違いやアドバイスなどありましたらコメントかhp0isme@gmail.comまでお願いします。



each


_.each(list, iteratee, [context])

まず、これとか

forEach

これ

for(i=0;i<obj.length;i++)

をイメージしてから入ります。

listを総なめして1回ずつ関数(iteratee)を実行します。

その関数の引数はlistの値(list[i])を使います。

ざっとでも使えると思いますが、細かい挙動もまとめてみます。

eachは3パターンあり、書き方によって自動的に挙動が変わります。


each ①listが配列

配列内を1つずつ総なめして処理。

引数は値とインデックスが渡されます。


each_1.js

_.each([10, 20, 30],function(elm,i){

console.log(i,elm);
});
// 0 10
// 1 10
// 2 10


each ②listがオブジェクト

配列じゃなくてオブジェクトだったなら、関数処理部分の引数が変わり、3つ(value,key,list)渡されます。

ですがメインで使うのはvalueとkeyの2つでしょう。

まぁ、配列と同じ感覚でOK。


each_2.js

_.each({x:10,y:22,name:"enemy"},function(val,key){

console.log(key +"は"+ val);
});
// xは10
// yは22
// nameはenemy


each ③contextあり

contextとはthisを使うようなオブジェクト(クラスっぽいもの)と思っていいでしょう。

第3引数にcontextがあった場合は、関数内でthisが使えます。

context(文脈 = オブジェクト)を関数に注入できる感じ。


each_3.js

var context = {x:19,y:45};

_.each([10, 20, 30],function(elm,i){
console.log(i,elm,this.x,this.y * 2);
},context);
// 0 10 19 90
// 1 20 19 90
// 2 30 19 90



map


_.map(list, iteratee, [context])

上のeachを先にやったという前提でいきます。

eachと同じ流れの中で、mapは関数処理の結果を配列で返します。

mapも3パターンです。


map ①listが配列

それぞれの結果を配列に格納し、返します。


map_1.js

var result = _.map([1, 2, 3], function(num){

return num * 11;
});
console.log(result); // 11,22,33


map ②listがオブジェクト

配列の時と違うのは引数が3つ(value,key,list)になることです。

ですが基本的にはvalueとkeyを使うと思います。


map_2.js

var result = _.map({x:1,y:2,z:3}, function(val,key){

return key +"は" + val * 11;
});
console.log(result); // xは11,yは22,zは33


map ③contextあり

context(文脈 = オブジェクト)を関数に注入します。

そうすると下のようなこともできますね。


map_3.js

var context = {vx:20,vy:45};

var result = _.map([1, 2, 3], function(num){
return num * this.vx;
},context);
console.log(result); // 20,40,60



reduce


_.reduce(list, iteratee, [memo], [context])

mapと違うのは、返り値が単一の値です。(not配列)

同じ要領で順番に処理をして、memoに値をストックしていきます。

これは感覚的にコードを追うと理解が捗るパターンか。


reduce ①通常

順番に値を足していきます。その結果をmemoにストックします。


reduce_1.js

var sum = _.reduce([1, 2, 3], function(memo, num){

return memo + num;
},0);
console.log(sum); // 6


reduce ② memoの初期値を変える

memoの初期値を変えてみます。


reduce_2.js

var sum = _.reduce([1, 2, 3], function(memo, num){

return memo + num;
},10);
console.log(sum); // 16


reduce ③memoを使わない

memoを使わなかったら、あんまり意味がなさそうです。


reduce_3.js

var sum = _.reduce([1, 2, 3], function(memo, num){

return num;
},0);
console.log(sum); // 3



reduceRight


_.reduceRight(list, iteratee, memo, [context])

reduceの順番を右からやるだけ。(方法はreduceを)


reduceright.js

var result = _.reduceRight(["reverse","&","death"], function(memo, tex){

return memo + tex;
},"");
console.log(result); // death&reverse



find


_.find(list, predicate, [context])

listを総なめして関数内の条件に合うもの(trueになるもの)を探索します。

複数ある場合は最初に見つかったもののみが返ります。

無かった場合はundefinedが返ります。

contextは以降割愛していきます。


find ①見つかった

最初の値を返す


find_1.js

var even = _.find([1, 2, 3, 4, 5, 6], function(num){

return num % 2 == 0;
});
console.log(even); // 2


find ②見つからない

undefinedを返す


find_2.js

var even = _.find([1, 2, 3, 4, 5, 6], function(num){

return num % 100 == 0;
});
console.log(even); // undefined



filter


_.filter(list, predicate, [context])

findと比較すると考えやすいでしょう。findと違うのは、見つかった値の全てを返してくれることです、配列で。

フィルターと言う言葉からして、値を何かの基準でフィルタリングするイメージ。


filter.js

var evens = _.filter([1, 2, 3, 4, 5, 6], function(num){

return num % 2 == 0;
});
console.log(evens); // [2,4,6]



where


_.where(list, properties)

複雑に見えますが、DBから引っ張ってきたデータなどをスマートに探索できそうです。

検索対象のオブジェクトを配列にまとめて第1引数に、第2引数に検索条件を入れます。

該当したオブジェクトが配列にまとまって返ってきます。


where.js

var human_1 = {name:"Christ",religion:"Christianity",birth:1};

var human_2 = {name:"Buddha",religion:"Buddhism",birth:-563};
var human_3 = {name:"Bill Gates",religion:"Christianity",birth:1955};
var human_4 = {name:"Steve Jobs",religion:"Buddhism",birth:1955};
var humansArr = [human_1,human_2,human_3,human_4];
var results = _.where(humansArr, {religion: "Buddhism"});
console.log(results);
//[human_2,human_4]というように該当オブジェクトが配列で返ってきます。
var results_2 = _.where(humansArr, {birth: 1955, religion:"Christianity"});
console.log(results_2); // Bill Gatesのオブジェクト



findWhere


_.findWhere(list, properties)

whereの単一版。最初に該当したオブジェクトのみが単一に返ってきます。


findwhere.js

var human_1 = {name:"Christ",religion:"Christianity",birth:1};

var human_2 = {name:"Buddha",religion:"Buddhism",birth:-563};
var human_3 = {name:"Bill Gates",religion:"Christianity",birth:1955};
var human_4 = {name:"Steve Jobs",religion:"Buddhism",birth:1955};
var humansArr = [human_1,human_2,human_3,human_4];
var results = _.findWhere(humansArr, {religion: "Buddhism"});
console.log(results); // Buddhaのオブジェクト



reject


_.reject(list, predicate, [context])

filterの逆版。条件に合わなかったものを(reject = 拒否して)配列で返します。

個人的にはリジェクトと聞くとあれしか思い浮かばない()


reject.js

var odds = _.reject([1, 2, 3, 4, 5, 6], function(num){

return num % 2 == 0;
});
console.log(odds); // [1, 3, 5]



every


_.every(list, [predicate], [context])

すべての値が評価条件をクリアーした場合のみtrueを返します。

これはシンプル。


every_1.js

var result = _.every([1, 2, 3, 4], function(num){

return num > 0;
});
console.log(result); // true

ひとつでもクリアーしなければfalseです。


every_2.js

var result = _.every([1, 2, 3, 4], function(num){

return num > 1;
});
console.log(result); // false



some


_.some(list, [predicate], [context])

everyと重なります。違うのは、ひとつでもクリアすればtrue。


some.js

var result = _.some([1, 2, 3, 4], function(num){

return num > 3;
});
console.log(result); // true



contains


_.contains(list, value)

list中にvalueがひとつでも含まれればtrueを返します。

だんだん名前だけでイメージできるようになってきましたか。


contains.js

var result = _.contains([1, 2, 3], 3);

console.log(result); // true



invoke


_.invoke(list, methodName, *arguments)

invoke = 呼びかける

第2引数の名前のメソッドを評価基準として再構築したものを返します。

ちょっとトリッキー感。


invoke.js

var result = _.invoke([[5, 1, 7], [3, 2, 1]], 'sort');

console.log(result); // [[1, 5, 7], [1, 2, 3]]



pluck


_.pluck(list, propertyName)

pluck = 引き抜く

mapなどの関数をより現実的に使いやすくしたもののようです。

オブジェクトが束になった配列からキーを指定して引き抜いて、配列にして返します。(んー、アルゴリズム的。)


pluck.js

var stooges = [{name: 'moe', age: 40}, {name: 'larry', age: 50}, {name: 'curly', age: 60}];

var result = _.pluck(stooges, 'name');
console.log(result); // ["moe", "larry", "curly"]



max


_.max(list, [iteratee], [context])

関数内のreturnで返した値を軸として最大値だったオブジェクトを返します。


max.js

var stooges = [{name: 'moe', age: 40}, {name: 'larry', age: 50}, {name: 'curly', age: 60}];

var result = _.max(stooges, function(stooge){
return stooge.age;
});
console.log(result); // Object {name: "curly", age: 60}



min


_.min(list, [iteratee], [context])

maxの逆版なので割愛。


min.js

var stooges = [{name: 'moe', age: 40}, {name: 'larry', age: 50}, {name: 'curly', age: 60}];

var result = _.min(stooges, function(stooge){
return stooge.age;
});
console.log(result); // Object {name: "moe", age: 40}



sortBy


_.sortBy(list, iteratee, [context])

関数内のreturnの値を軸に並べて配列にして返します。

サインの値が例になってるのが面白い。


sortby.js

var result = _.sortBy([1, 2, 3, 4, 5, 6], function(num){

return Math.sin(num);
});
console.log(result); // [5, 4, 6, 3, 1, 2]



groupBy


_.groupBy(list, iteratee, [context])

共通項をくくりだしてグループを作り、共通な仲間同士でオブジェクトを作って返します。

返ってくるのは{共通項 : [仲間A,仲間B] , 共通項B : [仲間A,仲間B]}の形式のオブジェクト。

こう見ると難しいけどニーズは「仲間を見つけたい時」


groupby.js

var result = _.groupBy([1.3, 2.1, 2.4], function(num){

return Math.floor(num);
});
console.log(result); // {1: [1.3], 2: [2.1, 2.4]}
var result_2 = _.groupBy(['one', 'two', 'three'], 'length');
console.log(result); // {3: ["one", "two"], 5: ["three"]}



indexBy


_.indexBy(list, iteratee, [context])

第2引数を基準としてオブジェクトを作ります。

しつこく言うと、とある配列[{オブジェクト}{オブジェクト}{オブジェクト}]だった時に、共通の基準があれば、

オブジェクト{基準:{オブジェクト},基準{オブジェクト},基準{オブジェクト}}みたいになります。

まぁ、コード見てみましょうか。

ちなみにstoogesとはアメリカのコメディーグループのことだとか。


indexby.js

var stooges = [{name: 'moe', age: 40}, {name: 'larry', age: 50}, {name: 'curly', age: 60}];

var result = _.indexBy(stooges, 'age');
console.log(result);
// {
// "40": {name: 'moe', age: 40},
// "50": {name: 'larry', age: 50},
// "60": {name: 'curly', age: 60}
// }



countBy


_.countBy(list, iteratee, [context])

関数内のreturnの値を基準としてカウントしてグループ化したオブジェクトを返します。

↑こういうの言葉では合っててもわかりにくい件。

形式は{基準A :個数 , 基準B,個数}


countby.js

var result = _.countBy([1, 2, 3, 4, 5], function(num) {

return num % 2 == 0 ? 'even': 'odd';
});
console.log(result); // {odd: 3, even: 2}



shuffle


_.shuffle(list)

とにかく配列をシャッフルします。

疲れたかシンプルな内容になってきた?


shuffle.js

var result = _.shuffle([1, 2, 3, 4, 5, 6]);

console.log(result); //[4, 1, 6, 3, 5, 2]



sample


_.sample(list, [n])

ランダムにひとつをサンプリングして取ってきます。

なかなか便利。


sample_1.js

var result = _.sample([1, 2, 3, 4, 5, 6]);

console.log(result); // 4

nに数値を入れるとその数だけ取ってきて配列で返します。


sample_2.js

var result = _.sample([1, 2, 3, 4, 5, 6],3);

console.log(result); // [1, 6, 2]



toArray


_.toArray(list)

配列を作ります。(!)

トリッキーです。

ぶっちゃけ、argumentsというJavaScript固有のルールを活用するためのものですかね。


toarray.js

(function(){ return _.toArray(arguments).slice(1); })(1, 2, 3, 4); // [2, 3, 4]


ちなみに、argumentsとはどんな関数にも強制的に入るオブジェクトです。

それの実態は引数です。lengthを使って主に引数の数を数えるのに使うようです。

たとえば


tsukareta.js

function tsukareta(tex1,tex2){

console.log(arguments);
}
tsukareta("もうすぐ","寝ます"); // 2

こういうのを使うのが中級者〜なようです。



size


_.size(list)

サイズを返すようですが、配列ではlengthプロパティがあるので使わなくてよいはずです。主にオブジェクトに使うと良いでしょう。


size.js

var result = _.size({one: 1, two: 2, three: 3});

console.log(result); // 3



partition


_.partition(array, predicate)

partition = 分割

第2引数のメソッドのreturnによって配列を分割して返します。

けっこうトリッキーな印象が。


partion.js

var result = _.partition([0, 1, 2, 3, 4, 5], isOdd);

console.log(result); // [[1, 3, 5], [0, 2, 4]]

長かった・・・

でもCollectionが一番多いので・・()