Dart における高階関数を使ったデータ処理
最近ではコンテナークラスなどのデータを高階関数を使って処理する手法が広がってきています。
今回はその高階関数を使ったデータ処理を Dart で行う方法について紹介します。
高階関数を使ったデータ処理
まず、高階関数を使ったデータ処理について説明します。プログラミング言語の傾向
高階関数を使ったデータ処理は Lisp や関数型言語でよく使われてきた方法で、 関数型プログラミングには欠かせないものです。しかし、関数型言語でなければ使えないというものではありません。
高階関数を使ったデータ処理はコードを短くかつわかりやすく記述することができます。 そのため、 Ruby, C#(LINQ) を始めとした多くの言語で取り入れられてきました。
- C# やるなら LINQ を使おう | プログラマーズ雑記帳
- 特集:人気言語でのデータ処理の比較: C#/ Scala / Python / Ruby / F#でデータ処理はどう違うのか? (1/3) - @ IT
処理の対象
これまで処理の対象をデータと表記していますが、 リスト(配列)のようなコンテナーだけでなく、 様々なものに対しても処理を行うことができるためです。Dart では具体的には Iterable クラスを継承したクラスで使うことができます。 Dart のコンテナークラスは Map(連想配列) を除いて、この Iterable を継承しています。

さらに、 Dart には Mix-in の機能があるため、 IterableMixinを使えば、 自作したコンテナークラスでもこれから紹介する多くのデータ処理用のメソッドが使えるようになります。
また、ファイル等の並列処理のための Stream クラスも Iterable を継承してはいませんが、 同じようなメソッドを持っています。
逐次処理
データ処理の中でもっとも基本的な逐次処理を例に高階関数を使う方法を説明します。C, C++ 風にリストの要素に順に処理していく場合にはループカウンターを使うと思います。
var lis = ["foo", "bar", "baz"]; for (var cnt = 0 ; cnt < lis.length ; cnt++) { print(lis[cnt]); } // foo // bar // bazこれを Dart では
for-in
を使って書くことできます。
for (var elem in lis) { print(elem); }ループカウンターを使うよりも短く書けますし、リストの要素を処理しているということが分かりやすくなっていると思います。
これを高階関数を使って書きなおしてみます。
高階関数のデータ処理では forEach を使います。 渡す関数は、無名関数を使っていますが、通常の関数でも構いません。
lis.forEach( (elem) => print(elem) ); // 無名関数の別の書き方 lis.forEach( (elem) { print(elem); } );
for-in
版とそんなには変わりません。
ですが、これは逐次処理に限って言語に制御文が用意されているからです。高階関数のデータ処理では逐次処理にかぎらず、処理が短くかつ分かりやすく書けるようになります。
次の章からその高階関数を使ったできるデータ処理の基本的なものについて見てきます。
なお
forEach
も後で説明する"連結"で使えるため、いらないというわけでもありません。
基本的なメソッド
それでは、ここから高階関数を用いたデータ処理でまず最初に覚えておいた方がよいと思われる機能について説明していきます。主要な機能は次の 5 つです。- 逐次処理(forEach)
- 写像(map)
- フィルター(where)
- 並び替え(sort)
- 畳み込み(fold, reduce)
map : 写像
写像というのは map, mapping と呼ばれる処理で、 配列などのコレクションのメンバーに一つずつ関数を適用して 戻り値で新しいコレクションを作ります。
Dart ではそのまま map という関数名です。
var src = [3, 2, 9, 6]; var mapped = src.map((elem) => elem * 2); // [6, 4, 18, 12]map に渡す関数は要素の型を引数にとる関数で、その戻り値が新しいコレクションの要素となります。 map は渡す関数の戻り値の型を変えれば、 値を変えるだけでなく、型を変えた新しいコレクションも作ることができ、 用途の広いメソッドです。
where : フィルター
フィルターはコレクションの中から条件にあう要素を取り出す処理です。
Datt では where() という名前になっています。
src = [3, 2, 9, 6]; var filtered = src.where((elem) => elem % 2 == 1); // [3, 9]データ処理で重要なメソッドをさらに絞るとすると、前節のマップとこのフィルターが、 特に重要度が高いです。
sort : 並び替え
sort は要素の並び替えを行います。基本的な処理に入れましたが、並び替えは逐次的な処理では無いため少し特殊です。 そのせいか Dart では Iterable には含まれておらず、 List のメソッドとなっています。
sort では何も指定しなければ、要素の compareTo を使って比較します。 比較用のメソッドを渡すと比較の基準を変えることができます。
lis = [-3, 2, -9, 0]; lis.sort(); // [-9, -3, 0, 2] lis.sort((x, y) => x.abs().compareTo(y.abs())); // [0, 2, -3, -9]
fold, reduce : 畳み込み(縮退)
畳み込みは要素を順に取得し、それらを計算に使った結果を返す処理です。

畳み込みは他言語では fold, reduce, inject などの関数名が使われます。 Dart では fold, reduce の 2 つが用意されています。
先に reduce を見ていきます。
src = [3, 2, 9, 6]; var sumval = src.reduce((sum, elem) => sum + elem); // 20 var maxval = src.reduce((max, elem) => (max < elem) ? elem : max); // 9reduce に渡す関数は 2 つの引数を取り、 2 つ目が各要素で、 関数の戻り値が次に呼ばれた時の 1 つ目の引数です。 これにより、各関数の結果を重ねていったものが reduce の戻り値として得られます。
1 番最初に呼ばれる場合、 1 つ目の要素は最初の要素です。
1 番最初に呼ばれる場合の初期値を指定する場合に fold を使います。
src = [3, 2, 9, 6]; var leng = src.fold(0, (count, elem) => count+1); // 4 var cons = src.fold("", (str, elem) => str += elem.toString() + ' '); // "3 2 9 6 "
その他のメソット(プロパティー)
データ処理の定番のメソッド以外で、抑えておいた方がいいかなと思うものも挙げて置きます。- length、 isEmpty
-
要素数の取得と空かどうかのチェック (プロパティー)
var src = [3, 2, 9, 6]; src.length; // 4 src.isEmpty; // false
- take
-
指定した要素数の取り出し。
var src = [3, 2, 9, 6]; src.take(2); // (3, 2)
- firstWhere, lastWhere
-
指定した条件に最初(最後)にマッチする要素の検索。
検索もデータ処理ではよく行う処理ですが、 条件にあうすべての要素を取得するのがフィルター(where)で、 最初の要素を取得するのが firstWhere です。var src = [3, 2, 9, 6]; src.firstWhere((elem) => elem % 2 == 1); // 3 src.lastWhere( (elem) => elem % 2 == 1); // 9
- contains
-
要素を含んでいるかの判定。
var src = [3, 2, 9, 6]; src.contains(9); // true src.contains(5); // false
- any, every
-
要素どれか一つ(すべて)が条件を満たすかどうかの判定。
var src = [3, 2, 9, 6]; src.any( (elem) => elem % 3 == 0); // true src.every((elem) => elem % 3 == 0); // false
メソッドの連結
map や where の返すものをちゃんと見てみると、iterable を返しています。var src = [3, 2, 9, 6]; var result = src.map((elem) => elem * 2); print(result); // (6, 4, 18, 12) print(result.runtimeType); // MappedListIterableこのため、これらのデータ処理は連結して書いていくことができます。
var src = [3, 2, 9, 6]; src.where((elem) => elem % 2 == 1) .map((elem) => elem * 2) .forEach((elem) => print(elem)); // 6 // 18
遅延評価
メソッドを連結して書くとメモリーがもったいないと思われる人もいるかもしれません。しかし、 map 等の返す iterable は特殊なもので、 必要になるまで実行されないという遅延評価の機能があります。 前節の例では forEach で一つずつ取り出す時が実行のタイミングです。
連結の処理は一見、次のように処理してるように見えます。
where map {3, 2, 9, 6} → {3, 9} → {6, 18}しかし、実際には遅延評価により、メソッドごとに結果をためるのではなく、 1 要素ずつ流すように次のメソッドに渡していきます。
where map 3 → 3 → 6 2 → ☓ 9 → 9 → 18 6 → ☓
なお、ソート(sort)は全要素が揃わないと完了しない処理なので、 言語によって扱いが変わってきます。
Dart ではソートでは遅延処理はしないという方針です。
そのため、 toList で一旦リストに変えてから List のメソッドでソートする必要があります。 このリストにする時が "必要なとき" となり、その時点で評価が行われます。
var src = [3, 2, 9, 6]; var resultsort = src.where((elem) => elem % 2 == 1) .map((elem) => elem * 2) .toList(); resultsort.sort((x, y) => y.compareTo(x)); print(resultsort); // [18, 6]
サンプルコード
説明で使用したサンプルのコードは以下のリンクからダウンロード(リンク先を保存)できます。 コンパイルする場合は以下のコマンドを実行します。> dart transform.dartDart をコマンドラインから使う方法については以前の記事を見て下さい。
- 関連記事
-
- Dart のインストールとコンパイル (Windows)
- Dart 用 Emacs モード
- Dart をコマンドラインから使う
- Dart における高階関数を使ったデータ処理
Facebook コメント
コメント