lodash(Underscore.js)まとめ, Swift, Objective-C, PHPもあるよ

  • 62
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

本投稿のgistはこちら
https://gist.github.com/manchan/cc72225b813bea47d1d0

lodash(Underscore.js)

JavaScriptのユーティリティライブラリ。
2015年5月現在、npmでもっとも多く参照されている。
https://www.npmjs.com/browse/depended

lodashはUnderscoreの派生ライブラリ。
Backbone.jsは、もともとunderscore.jsに依存していますし、
lodashは、JavaScriptトランスパイラのBabel,ブログプラットフォームのGhost,
プロジェクトのベースとなるツールであるYeomanなど,npmの主要なパッケージのいくつかは依存している。

AngularJsでDI(依存性注入)してlodash(underscore.js)をモジュール化して、そのまま使用してもいい。
Reactでももちろん使用可能。

lodash(Underscore.js)を使用する目的

配列やオブジェクトの操作をする際に便利
コードの可読性アップ
つまりコードをシンプルかつ短縮できる
for文、if文を多用せずに済む
つまり保守性が高い(変数の状態を知らなくて済む, 関数は参照透過性が保たれている)

Underscoreとlodashの比較

Lo-Dashの方が機能数が多い
Lo-Dashの方が高速、パフォーマンスがいい
native vs. array.js vs. underscore vs lo-dash
http://jsperf.com/native-vs-array-js-vs-underscore/8

lodash(Underscore)便利メソッド集

// latest 3.7.0
var _ = require('lodash');

/**
 * lodash(Underscore.js) の便利な関数ライブラリたち
 * 「_」というオブジェクトを使用することにより利用可能
 * 配列、オブジェクト操作を便利にするもの
 *
 * よく使うのは以下
 * each
 * find
 * filter
 * where
 * findWhere
 * contains
 * pluck
 * max
 * min
 * sortBy
 */

Collection functions

each

/**
 * ループ処理 collection each
 * => each: 4
 * => each: 10
 * => each: 16
 */
_.each([2, 5, 8], function(num){
    console.log( 'each: ' + num * 2 );
});

for と mapの例

/**
 * jsのfor文で配列に要素を追加
 * => count_arr:2,4,6,8,10
 */
var count_arr = [];
for(var i = 1; i < 6; i++){
    count_arr.push(i * 2); // マッピングのたびに外部の変数の状態が変化する ☓
}
console.log('count_arr:' + count_arr);

/**
 * mapで配列を操作
 * 返り値は配列
 * => map: 2,4,6,8,10
 */
var map = _.map(_.range(1, 6), function(num){
    return num * 2; // 変数の状態を考えずに済む ○
});
console.log( 'map: ' + map );

for と reduce × filterの例

/**
 * 文字列配列の内、三文字以上のものを選択し、
 * 文字数の合計を集計する
 * => 18
 */
var cities = ["京都", "サンクトペテルブルク", "ボゴダ", "東京", "ゴア", "バルセロナ"];
var total_str_len = 0;
for(var city in cities){
    var str_len = cities[city].length;
    if(str_len >= 3){
        total_str_len += str_len; // マッピングのたびに外部の変数の状態が変化する ☓
    }
}
console.log( 'for total_str_len:' + total_str_len); // => 18


/**
 * 上記をreduce × filter でやる
 * reduceの返り値は単一の値
 * filterにて、条件で絞った配列に対して, reduceで一つずつの要素に対して処理をする。
 * memoに結果がストックされる。第三引数0はmemoの初期値
 * => 18
 */
var total_len = _.reduce(
    _.filter(cities,function(city){return city.length >= 3;}),
    function(memo, str){ return memo + str.length;}, 0);
console.log( 'function total_len:' + total_len); // 18

chain

/**
 * chain メソッドチェーンで使用
 * value()でchain結果を取得
 * => 18
 */
var total = _.chain(cities)
    .filter(function(city){ return city.length >= 3 })
    .reduce(function(memo, str){ return memo + str.length;}, 0)
    .value();
console.log( 'function total:' + total); // 18

flatMapを使用したい場合(カスタム)

/**
 * flatMapを使用したい場合
 */
var flatMap = _.flatMap = _.concatMap = function (xs, f, self) {
        return _.reduce(xs, function (ys, x) {
            return ys.concat(f.call(self, x));
        }, []);
    };
_.each(['flatMap', 'concatMap'], function (name) {
    _.prototype[name] = function (f, self) {
        return _(flatMap(this.value(), f, self));
    };
});


/**
 * 以下、
 * 整数リストのリスト中から奇数だけを取り出して並べたリストを作成
 */

// 普通にやるとこう filter ✕ map
var filterMap = _.filter(_.range(1, 11), function(x){
    return x % 2 === 1;
}).map(function(x){
        return x;
});
console.dir( 'filterMap:' + JSON.stringify(filterMap, null, 4));

// filter ✕ reduce
var filterReduce = _.filter(_.range(1, 11), function(x){
    return x % 2 === 1;
}).reduce(function(flattened, other){
        return flattened.concat(other);
},[]);
console.dir( 'filterReduce:' + JSON.stringify(filterReduce, null, 4));

// flatMap使用
var flatMap = _.flatMap(_.range(1, 11), function (x) {
    return x % 2 === 1 ? [x] : [];
});
console.dir( 'flatMap:' + JSON.stringify(flatMap, null, 4));

// 同等
var concatMap = _.concatMap(_.range(1, 11), function (x) {
    return x % 2 === 1 ? [x] : [];
});
console.dir( 'concatMap:' + JSON.stringify(concatMap, null, 4));

lodashにはtransformがあった!

/**
 * transform
 * lodashにはtransformがあった!
 * Underscore.jsのreduceの代替
 * 結果は上記と同じ
 */
var transform_arr = _.transform(_.range(1, 11), function(result, x, i) {
    return x % 2 === 1 ? result.push(x): [];
});
console.dir( 'transform:' + JSON.stringify(transform_arr, null, 4));

find

var a = [1, 3, 6, 9];

/**
 * find
 * 集合要素に対しての操作
 * 一番目の要素を探して返す
 * => find: 6
 */
console.log( 'find: ' + _.find(a, function(num){
    return num > 5; // 6
}));

where

/**
 * where
 * 条件に合致するオブジェクトを返す
 */
console.log( 'where:' + JSON.stringify(
    _.where(PlayList, {artist : "Pharrell Williams"}), null, 4)
);

findWhere

/**
 * findWhere
 * 条件に合致する最初のオブジェクトを返す
 */
console.log( 'findWhere:' +
    JSON.stringify(
        _.findWhere(PlayList, {artist : "Pharrell Williams"}), null, 4
    )
);

filter

/**
 * filter
 * 条件に一致したものを配列で返す
 * => filter: 6,9
 */
z = _.filter(a, function(num){
    return num > 5;
});
console.log( 'filter: ' + z ); // 6, 9

contains

/**
 * contains
 * 配列内に指定値があるか => true or false
 * => contains: false
 */
console.log( 'contains: ' + _.contains(a, 10));

groupBy

/**
 * groupBy
 * 結果によるグループ化
 * groupBy: [object Object]
 */
var groupResult = _.groupBy([1, 2, 5, 8, 42, 12], function(num){
    return num % 3;
});
console.dir( 'groupBy:' + JSON.stringify(groupResult, null, 4));

countBy

/**
 * countBy
 * グループの要素カウント
 * => Object {odd: 2, even: 4}
 */
var countResult = _.countBy([1, 2, 5, 8, 42, 12], function(num){
    return num % 2 == 0 ? 'even' : 'odd';
});
// => Object {odd: 2, even: 4}
console.dir( 'countResult:' + JSON.stringify(countResult, null, 4));

sortBy

/**
 * sortBy
 * => sortBy Math sin: 5,42,12,1,2,8
 */
console.log( 'sortBy Math sin: ' + _.sortBy([1, 2, 5, 8, 42, 12], function(num){
    return Math.sin(num);
}) );

/**
 * sortBy
 * => sortBy length: i,me,and,you,dog,father
 */
console.log( 'sortBy length: ' + _.sortBy(['i', 'me', 'and', 'you', 'father', 'dog'], 'length') );

union

var c = [1, 2, 5];
var d = [2, 4, 6];

/**
 * union
 * 配列の結合(ユニーク)
 * => union: 1,2,5,4,6
 */
console.log( 'union: ' + _.union(c, d) );

intersection

/**
 * intersection
 * 配列の共通箇所を抽出
 * => intersection: 2
 */
console.log( 'intersection: ' + _.intersection(c, d) );

difference

/**
 * difference
 * 第一引数の配列を元に、比較して違いを配列で返す
 * => difference: 1,5
 */
console.log( 'difference: ' + _.difference(c, d) );

uniq

/**
 * uniq
 * => uniq: 2,5,10
 */
console.log( 'uniq: ' + _.uniq([2,5,10,2, 5]) );

template

/**
 * template
 * テンプレート機能 テンプレートを引数で置換
 * <% %> js evaluate
 * <%= %>  interpolate
 * <%- %> escape output
 */
var js_ = "<% console.log('hello from tmpl'); %>";
var html_tpl = "<li><%- name %> - (<%- age %>)</li>";
console.log(_.template(
    "Using 'with': <%= data.answer %>",
    {variable: 'data'})({answer: 'no'})); // => Using 'with': no

Object function

keys

/**
 * オブジェクト操作
 * @type {{name: string, age: number, url: string}}
 */
var user = {
    name: 'matsuoka',
    age : 27,
    url : 'http:manchan.github.io'
};

// => keys: name,age,url
console.log( 'keys: ' + _.keys(user) );

values

// => values: matsuoka,27,http:manchan.github.io
console.dir( 'values: ' + _.values(user) );

invert

// => Object {27: "age", matsuoka: "name", http:manchan.github.io: "url"}
console.dir( 'invert:' + JSON.stringify(_.invert(user), null, 4) );

extend 参照先もコピーされるため,参照元の値を上書きするおそれがある ※ディープクローンしたい場合は、cloneDeep

/**
 * extend
 * _.extend(destination, *sources)
 * soucesオブジェクトのすべてのプロパティをdestinationオブジェクトにコピーする。
 * sourcesの中に同じ名前のプロパティが含まれていた場合、より後のもので上書きされる。
 */
    // => {name : 'matsuoka', age : 27}
console.dir( 'extend:' + JSON.stringify(_.extend({name : 'matsuoka'}, {age : 27}), null, 4));

var firstObj = {firstObj: 1},
    secondObj;
secondObj = _.extend(firstObj, {secondObj: 2});
console.log(firstObj);  // {firstObj: 1, secondObj: 2}
console.log(secondObj);  // {firstObj: 1, secondObj: 2}
secondObj.firstObj = 3;
console.log(firstObj);  // {firstObj:3, secondObj:2} !上書きされている
console.log(secondObj);  // {firstObj:3, secondObj:2}

defaults

/**
 * defaults
 * _.defaults(object, *defaults)
 * objectがdefaultsオブジェクトのプロパティを持っていない場合、
 * objectにそのdefaultsオブジェクトのプロパティを付与する。
 */
var iceCream = {flavor : "chocolate"};
// => {flavor : "chocolate", sprinkles : "lots"}
console.dir( 'defaults:' + JSON.stringify(
    _.defaults(iceCream, {flavor : "vanilla", sprinkles : "lots"}),
    null,
    4));

clone (shallow copy)

/**
 * clone
 * _.clone(object)
 * objectのシャローコピーを作る。
 * ネストされたオブジェクトや配列は、参照コピーされる
 */
var clone_obj = _.clone({name : 'matsuoka'});
// => {name : 'matsuoka'}
console.dir( 'clone:' + JSON.stringify(clone_obj, null, 4));

has

// => has: true
console.log( 'has: ' + _.has(user, "name"));
// => false
if( !_.has(clone_obj, 'foo') ){
    // error
    // return;
}

is系(isUndefined, isEmpty, isStringなど)

/**
 * is系は多い
 * => true or false
 */
    // => isEmpty user.name: false
console.log( 'isEmpty user.name: ' + _.isEmpty(user.name));
// => isNull user.age: false
console.log( 'isNull user.age: ' + _.isNull(user.age));
// => isString user.age: false
console.log( 'isString user.age: ' + _.isNull(user.age));
// => isNumber user.age: true
console.log( 'isNumber user.age: ' + _.isNumber(user.age));

shuffle

/**
 * 関数的シャッフル
 * => shuffle1: 3,8,2,4
 */
console.log( 'shuffle1: ' + _.shuffle([2,3,8,4]) );
// OOP
console.log( 'shuffle2: ' + _([2,8,10,3]).shuffle() );

range

// => range 1,10: 1,2,3,4,5,6,7,8,9
console.log( 'range 1,10: ' + _.range(1, 10) );
// => range 0,10: 0,1,2,3,4,5,6,7,8,9
console.log( 'range 0,10: ' + _.range(10) );
// => range 1,11,2: 1,3,5,7,9
console.log( 'range 1,11,2: ' + _.range(1, 11, 2) );

random

// => random 0, 10: 2
console.log( 'random 0, 10: ' + _.random(10) );

escape

// => escape: &lt;script&gt;
console.log( 'escape: ' + _.escape('tags') );

times

// => 3 times of Hello! * 3
_.times(3, function(){
    console.log( '3 times of ' + 'Hello!');
});

// => Ruby っぽい 3 times of Hello! * 3
_(3).times(function(){
    console.log( ' Ruby っぽい 3 times of ' + 'Hello!');
});

Function () Functions

bind

/**
 * bind
 * オブジェクトを関数にバインドする。関数が呼ばれた時、
 * thisがobjectの値をとる。カリー化。
 */
var func = function(greeting){ return greeting + ': ' + this.name };
func = _.bind(func, {name : 'moe'}, 'hi');
// => hi: moe
console.log(func());

bindAll

/**
 * bindAll
 * 複数の関数をオブジェクトにバインドする。イベントハンドラーに関数をバインドするときに便利
 */
var hoge = {
    hello:function(){console.log(' bindAll: hello '+ this.name)},
    name: 'matsuoka'
};
_.bindAll(hoge, 'hello');

function Fuga(){}
var fuga = new Fuga();
fuga.name = 'you_matz';
fuga.hello = hoge.hello;
// => hello matsuoka
fuga.hello();

memoize

/**
 * memoize
 * _.memoize(function, [hashFunction])
 * 関数の計算結果をキャッシュする。メモ化。
 * hasFunctionが指定された場合、結果をストアするためのハッシュキーとして利用される。
 * hasFunctionのデフォルト値は一番最初の引数。
 */
var fibonacci = function(n) {
    return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
};
var fastFibonacci = _.memoize(fibonacci);
// => 10946
console.log( 'memoize:' + fastFibonacci(21));

delay

/**
 * delay
 * _.delay(function, wait, [*arguments])
 * setTimeoutのように、ミリ秒後に関数を呼び出す。argumentsを指定した場合、それが呼び出される関数に引き渡される。
 */
var log = _.bind(console.log, console);
// => 'logged later' // Appears after one second.
_.delay(log, 1000, 'logged later');

defer

/**
 * defer
 * コールスタックが空になるまで、関数の呼び出しを遅延させる。
 * delay = 0でsetTimeoutを使う場合と同じ。
 * 複雑な計算を行ったり、UIスレッドをブロックせずにまとまったHTMLレンダリングを行いたいときに便利。
 */
_.defer(function(){ alert('deferred'); });
// Returns from the function before the alert runs.

小技、便利な組み合わせ例

/**
 * pluck × indexOf (findIndexで代替可能)
 * 指定した配列内のプロパティの値が、何番目に位置しているか
 *
 * pluck
 * オブジェクトの配列に使うと便利なメソッド
 * 第一引数に渡したオブジェクトの配列の中から第二引数で渡したプロパティに対応する値を配列で返します。
 * 今回の場合は「PlayList」のそれぞれのオブジェクトの値のなかから「name」の値が配列として返ってきています。
 *
 * そして、
 * indexOf
 * nameの配列から、第二引数で指定したsongが存在すればindexを返す。
 * なければ-1を返す
 */
var PlayList = [
    {
        artist: "Pharrell Williams",
        name: "Marilyn Monroe",
        playcount: 97846,
        listeners: "32394",
        url: "http://www.last.fm/music/Pharrell+Williams/_/Marilyn+Monroe"
    },
    {
        artist: "Pharrell Williams",
        name: "Come Get It Bae",
        playcount: 113715,
        listeners: "38660",
        url: "http://www.last.fm/music/Pharrell+Williams/_/Come+Get+It+Bae"
    },
    {
        artist: "Pharrell Williams",
        name: "Happy",
        playcount: 369065,
        listeners: "98331",
        url: "http://www.last.fm/music/Pharrell+Williams/_/Happy"
    }
];

var song = "Happy";
// index search
var index = _.indexOf(_.pluck(PlayList, 'name'), song);
// => indexOf × pluck : :2
console.dir(" indexOf × pluck :" + index);


/**
 * filter × map
 */
var filtered_playlist = PlayList.filter(function(list) {
    return list.playcount > 100000; // 真であれば、keepする
}).map(function(list) {
        return { // return what new object will look like
            songname: list.name,
            playcount: list.playcount,
            songurl : list.url
        };
    });

console.log( 'filter × map:' + JSON.stringify(filtered_playlist, null, 4));


/**
 * reduce × filter
 */
var filtered_song_middle = _.reduce(_.filter(PlayList, function(list){
    return list.playcount > 300000;
}),function(memo, list){
    memo.push({songname: list.name})
    return memo;
}, []);

console.dir( 'reduce × filter:' + JSON.stringify(filtered_song_middle, null, 4));


/**
 * reduceのみの場合
 */
var reduce_playlist = PlayList.reduce(function(memo, list) {
    if (list.playcount > 300000) { // filterとして
        memo.push({ // mapとして
            songname: list.name,
            playcount: list.playcount,
            songurl : list.url
        });
    }
    return memo;
}, []);

console.dir( 'reduce:' + JSON.stringify(reduce_playlist, null, 4));

/**
 * transformで少しスリムに
 */
var transform_playlist =  _.transform(PlayList, function(result, list, i) {
    return list.playcount > 300000 ? result.push({
        songname: list.name,
        playcount: list.playcount,
        songurl : list.url
    }): [];
});

console.dir( 'transform_playlist:' + JSON.stringify(transform_playlist, null, 4));


/**
 * max
 * 一番大きい値のオブジェクトを返す
 */
var maxplay = _.max(PlayList, function(list){
    return list.playcount;
});

console.log( 'max:' +
    JSON.stringify(maxplay, null, 4));


/**
 * filter × max
 * 指定した条件内にて一番大きい値のオブジェクトを返す
 */
var filter_max = _(PlayList.filter(function(list) {
    return list.playcount < 100000; // 真であれば、keepする
})).max(function(list){
        return list.playcount;
    });

console.dir( 'filter ✕ max:' +
    JSON.stringify(filter_max, null, 4));


/**
 * メソッドチェーン
 * value()で値取得
 * => chain value: 2,4,6
 */
console.dir( 'chain value: ' + _.chain([1, 2, 3])
    .shuffle()
    .map(function(num){
        return num * 2;
    })
    .value());


/**
 * オブジェクトのサイズを取得する
 * 標準jsの場合とUnderscoreの場合
 */
console.log( ' object length:' + Object.keys(PlayList).length);
console.log( ' object length by underscore:' +_.size(PlayList));


/**
 * parse object to array
 * オブジェクトを配列に変換
 */
_.each(PlayList, function(elem, key){
    PlayList[key] = _(elem).values();
});

console.log(' parse object to array: ' + PlayList);


/**
 * 文字列をスライスして配列にコンバート
 */
var arguments = "1234";
var testToArray = _.toArray(arguments).slice(0,4);
console.log(_.toArray(arguments));
console.log(' testToArray: ' + testToArray);


Underscoreにないlodash便利メソッド

/**
 * findIndex オブジェクトのindexを取得
 * cloneDeep まるごとオブジェクトコピー(deep cloning,参照はコピーされない)
 * transform 引数に渡した配列、オブジェクトを変化させる(reduceよりもシンプルに書ける)
 */
// 以下同じ
// var indexByLodash = _.findIndex(PlayList, { 'name': song });
var indexByLodash = _.findIndex(PlayList, function(list) {
    return list.name == song;
});
console.dir(" findIndex by lodash :" + indexByLodash);

// cloneDeep _.cloneDeep(value, [customizer], [thisArg])
var deep = _.cloneDeep(personList);
var result2 = deep[0] === personList[0];
console.log('cloneDeep by lodash:' + result2); // false

// transform
var tsf = _.transform([1, 2, 3, 4, 5], function(memo, idx) {
    memo[idx] = true;
}, {});

console.log('transform:' + JSON.stringify(tsf, null, 4));

他の言語でのlodash(Underscore)

Objective-C

Underscore.m http://underscorem.org/

Podfile
#CocoaPodsを利用
pod 'Underscore.m'
pod install
main.m
#import <Underscore.h>

    NSArray *array = @[@1, @2, @3, @4, @5, @6, @7];
    id first = Underscore.array(array).first; // @1が返る
    NSLog(@"first : %@", first);

    NSArray *array2 = @[@2, @4, @6, @8, @10, @12, @14];
    // クラスメソッドの場合
    id first2 = Underscore.first(array2); // @2が返る
    NSLog(@"first2 : %@", first2);

    NSArray *array3 = @[@1, @2, @3, @4, @5, @6, @7];
    id last = Underscore.array(array3).last; // @7が返る

    NSLog(@"last : %@", last);

    NSArray *array4 = @[@"one", @"two", @"three"];
    NSArray *capitalized = Underscore.arrayMap(array4, ^(NSString *string) {
        return string.capitalizedString;
    }); // @[@"One", @"Two", @"Three"]が返る

    NSLog(@"arrayMap : %@", capitalized);


    /*
     例えば「言語をキーとして挨拶文を定義したNSDictionaryがあり、
     この挨拶文に含まれる単語の先頭を大文字に変換したものを配列として取得する。
     ただし挨拶文が必ず定義されている訳ではない」といった処理
     */
    NSDictionary *dictionary = @{
                                 @"en": @"Hello world!",
                                 @"sv": @"Hej världen!",
                                 @"de": @"Hallo Welt!",
                                 @"ja": [NSNull null] // 日本語だけ挨拶文が設定されていない
                                 };

    NSArray *capitalizedHello = Underscore.dict(dictionary)
    .values
    .filter(Underscore.isString)
    .map(^NSString *(NSString *string) {
        return [string capitalizedString];
    })
    .unwrap;

    NSLog(@"capitalizedHello:%@", capitalizedHello);

Swift

同等の機能を標準(Collection API)で利用できます

1.2からflatMapが追加されました

MyPlayground.playground

import UIKit


/************************************
 filter
 クロージャに抽出条件を書き、
 そのクロージャの返り値がtrueの場合のみの配列を返す
 クロージャのパラメータを $0, $1, $2, …のように省略することができます。
 パラメータを省略するとさらに短く書けます
************************************/

var ranges = [Int](1...10)
var evenArr = filter(ranges, {$0 % 2 == 0})
var evenArr2 = ranges.filter{ num in num % 2 == 0}
println(evenArr)
println(evenArr2)


/************************************
 map
 クロージャが返した値を返す
************************************/

var array = ["a10", "b10", "c11"]
var mappedArray = map(array, { $0 + " - 1"})
println("mapped: \(mappedArray)")

var numbers = [1,2,3,4]
let numTwice = numbers.map{
    num in num * 2
}
println(numTwice)


/************************************
 reduce
 配列の中の2要素から計算して、その結果を返す。
 さらにその結果から次の要素を計算して結果を返し、それを配列分繰り返した値を返す。
************************************/

var list1 = [1 ,2 ,3 ,4 ,5 ]
let list1total = list1.reduce(0){ (a:Int, b:Int) -> Int in
    return a + b
}
println("list1 total is \(list1total)")


var cities = ["京都", "サンクトペテルブルク", "ボゴダ", "東京", "ゴア", "バルセロナ"];
let filter_cities =
    cities.filter{ city in (city as NSString).length >= 3}
    .reduce(0){
        (memo:Int, city:String) -> Int in
        return memo + (city as NSString).length
    }

println( "filter ✕ reduce : \(filter_cities)");



/************************************
 flatMap (Swift 1.2~)
 配列の要素を加工し、フラットにする(一次元配列とする)
************************************/

var mixArr = ["2", "3", "four", "5", "six", "7"]
// 整数と文字列が入り混じった配列から整数のみを抽出する


// ※mapの場合
var mapRes = mixArr.map{ $0.toInt().map{ $0 } }
println(mapRes) // nilが混じり残念な結果に

// flatMap
var intgrouplong = mixArr.flatMap { (element: String) -> [Int] in
    if let number = element.toInt() {
        return [number]
    } else {
        return []
    }
}
// ワンライナー
var intgroupshort = mixArr.flatMap { $0.toInt().map { [$0] } ?? [] }
println(intgroupshort)

var oddArray = ranges.flatMap { (x: Int) -> [Int] in ( x % 2 == 1 ) ? [x] : [] }
var oddArray2 = ranges.filter{ num in num % 2 == 1 }
println(oddArray)
println(oddArray2)



let strings = ["aaaa", "bbb", "cc", "d"]
let bothCaseStrings =
strings.flatMap {
    str in [str.uppercaseString, str.lowercaseString]
}

println(bothCaseStrings)

PHP

Underscore.php http://brianhaveri.github.io/Underscore.php/

curl -sS https://getcomposer.org/installer | php
composer.json
{
    "require": {
        "underscore/underscore.php": "dev-master"
    }   
}
#package install
php composer.phar install
sample.php
<?php

require 'vendor/autoload.php';

/*map __::map(collection, iterator) Alias: collect
Returns an array of values by mapping each in collection through the iterator. Arguments passed to iterator are (value, key, collection). Unlike Underscore.js, context is passed using PHP's use statement.
*/
// array(3, 6, 9)
print_r( __::map(array(1, 2, 3), function($num) {
    return $num * 3;
}));

// array(3, 6, 9)
print_r( __::map(array('one'=>1, 'two'=>2, 'three'=>3), function($num, $key) {
    return $num * 3;
}));


/*
pluck __::pluck(collection, propertyName)
Extract an array of property values
*/
$stooges = array(
    array('name'=>'moe', 'age'=>40),
    array('name'=>'larry', 'age'=>50),
    array('name'=>'curly', 'age'=>60)
);

// array('moe', 'larry', 'curly')
print_r( __::pluck($stooges, 'name') );

参考資料

Lo-Dash本家 (http://lodash.com/docs)
Underscore本家 (http://underscorejs.org/)
lodash, あなたが既に使っているJavaScriptライブラリ
http://www.infoq.com/jp/news/2015/04/lodash-utility-library
native vs. array.js vs. underscore vs lo-dash
http://jsperf.com/native-vs-array-js-vs-underscore/8

npm - most depended-upon packages
https://www.npmjs.com/browse/depended

Undescore.m - a functional toolbelt for Objective-C
http://underscorem.org/

iOSでUnderscore.jsライクに処理できるUnderscore.m〜前編〜
http://dev.classmethod.jp/smartphone/iphone/ios-underscore-1/

Underscore.php
http://brianhaveri.github.io/Underscore.php/

CoffeeScriptでLodash(Underscore)を書く場合
Underscore Reference — Smooth CoffeeScript
http://autotelicum.github.io/Smooth-CoffeeScript/literate/underscore.html