前書き
自分ルールの中で、慣れない言語に触れるときに必ずやっているのが、データドリブン的に「Map, Filter, Reduce」が素直に記述できるかを確かめること。
慣れない言語を触らなければならない時に「あれ、map, filter, reduceっぽいことどうやるんだっけ?」ってなった時のリファレンスになれば幸い。
(5/26修正)
タグが5つまでしか付けられないことに気がついたので、やはり記事を分離します。すでに6つ書いてしまっていたので、特にコメント等に言及されてなかったF#を(2)に移すことにしました。
実際にやってみた
処理概要
- 10,000,000件のデータを生成(整数値で0〜9,999,999の値が入っている)
- 各要素を2倍にする
- 3の倍数である数値のみ抽出
- 抽出後の要素を総和する
対象言語
- C++ (C++11文法)
- Python(v3)
- Kotlin(v1.1.2)
- Swift(v3)
- Javascript (node v6.9.4)
環境
- MacBook Air (Early 2014)
- macOS Sierra 10.12.4
- Visual Studio Code (エディタ)
制限事項
- 言語特有の裏技を駆使して最速を目指すのは避けた(自分の中では普通に書いたつもり)
- 生成したデータはイテレータオブジェクトではなく、実体データの集合になるようにした
- map, filter, reduce処理呼び出しについては続けて呼ぶような記述にした
- 計算結果は全ての言語において
33333336666666
になっていることを確認済み - map処理の開始直前からreduce処理終了までの時間を参考までに計測した
コード
時間計測処理付きのコードはGithubに上げており、各言語のフォルダにあるmapFilterReduce.*
ファイルを参照のこと。
C++
#include <vector>
#include <algorithm> // transform, copy_if
#include <iterator> // back_inserter
#include <iostream> // cout
#include <numeric> // accumulate
using namespace std;
int main(){
// 1. Generating sequence
auto a = vector<long long>(10000000);
for (long long i=0; i < a.size(); ++i) a[i] = i;
// 2. Mapping the sequence into another
auto mapped = vector<long long>(10000000);
transform(a.begin(), a.end(), mapped.begin(), [](long long x){return x * 2;});
// 3. Filtering the sequence
vector<long long> filtered;
copy_if(mapped.begin(), mapped.end(), back_inserter(filtered), [](long long x){return x%3==0;});
// 4. Reducing the sequence
long long result = accumulate(filtered.begin(), filtered.end(), (long long)0, [](long long a, long long b){return a+b;});
// As a result
cout << result << endl;
return 0;
}
Python3
import functools # for reduce function
# 1. Generating sequence
a = list(range(10000000))
# 2. Mapping the sequence into another (Deferred execution)
mapped = map(lambda n:n*2, a)
# 3. Filtering the sequence (Deferred execution)
filtered = filter(lambda n:n%3==0, mapped)
# 4. Reducing the sequence
result = functools.reduce(lambda a,b:a+b, filtered)
# As a result
print(result)
Kotlin
fun main(args:Array<String>){
var a =
// 1. Generating sequence
IntArray(10000000) { it }
var result =
// 2. Mapping the sequence into another
a.map { n -> n * 2}
// 3. Filtering the sequence
.filter { n -> n%3 == 0}
// 4. Reducing the sequence
.fold(0L) { x, y -> x.toLong() + y.toLong()}
// As a result
println(result.toString())
}
Swift
let a =
// 1. Generating sequence
(0..<10000000)
let result =
// 2. Mapping the sequence into another
a.map {$0*2}
// 3. Filtering the sequence
.filter {$0 % 3 == 0}
// 4. Reducing the sequence
.reduce(0, +)
// As a result
print("\(result)")
JavaScript
// 1. Generating sequence
var a = new Array(10000000);
for (var index = 0; index < a.length; index++) a[index] = index;
// 2. Mapping the sequence into another
var mapped = a.map(n=>n*2);
// 3. Filtering the sequence
var filtered = mapped.filter(n=>n%3==0);
// 4. Reducing the sequence
var result = filtered.reduce((a,b)=>a+b);
// As a result
console.log(result);
考察
一通り記述してみた感触として
- C++は仕方ないにしろ大体同じような書き方で実現できることがわかった
- 無名関数の書き方が様々で、慣れの問題な気がしてきた
- 関数を繋げていけるものもあれば、できないものもある。
参考結果
計測値は揺らいでいたけど大体この前後ということで端数をカット。
言語 | 計測値(msec) | 備考 |
---|---|---|
C++ | 90 |
-O3 付きでコンパイル |
Python | 5000 | |
Kotlin | 3500 | |
Swift | 150 |
-Ounchecked 付きでコンパイル |
JavaScript | 11000 | v6以外なら2000msecぐらいになる |
※過去に計測したデータは編集履歴を見るとわかります。色々おかしかったところもありますが、良い勉強になりました。