再帰無しでmap/reduce
序
Illustratorで使えるscriptはES3のものらしい。
普段Elixirばかり書いているのでJSの事情は知らないのですがES5ままではmap/reduceなどのElixirでおなじみの関数は実装されておらず配列操作は for
を使うのが一般的らしい。
しかし今更for文を書く事には耐えられそうにないのでmap/reduceを実装することにした
再帰・無し
再帰関数が書ければそもそもfor文なんて書かなくて済む。と思って侮っていたらどうやらJSでは再帰関数を書けないらしい...。かなしい
不本意ながらforを使って便利関数を実装するしか無さそうだ...
reduce
使えないものは仕方ないのでreduceを実装する
var Enum = {};
Enum.reduce = function(arr, acm, f) {
var res = acm;
for (var i = 0; i < arr.length; i++) {
res = f(arr[i], res);
}
return res
}
var res = Enum.reduce([1,2,3], 0, function (i, acm) {
return acm + i;
})
console.log(res);
// => 6
副作用を出してしまっているけどまあ動いてるからヨシ!
map
reduceができたのでreduceを使ってmapを実装する。
Enum.map = function(arr, f) {
return Enum.reduce(arr, [], function(v, acm) {
acm.push(f(v));
return acm
});
}
var res = Enum.map([1,2,3], function(i) {
return i * 2;
});
console.log(res);
// => [2, 4, 6]
だいぶいい感じ!
each
イラレスクリプトでは結構手続き的な書き方をすることも多い。
別にmapでやっても良いんだけど後で読み返したときに戻り値は関係ないと伝えたいのでeachも実装しておく
Enum.each = function (arr, f) {
Enum.map(arr, function (v) {
f(v);
});
}
Enum.each([1,2,3], function (i) {
console.log(i);
});
// 1
// 2
// 3
mapをラップしただけだが意図は伝わる。と思う。
filter/reject
雑にレイヤー上の全テキストフレームを取ってきて特定の文字列が入力されているものだけを抽出するときもあったので実装する。
Enum.filter = function(arr, f) {
return Enum.reduce(arr, [], function(v, acm) {
if (f(v)) {
acm.push(v);
}
return acm;
});
}
ついでにfilterの対になるrejectも実装しておく
Enum.reject = function(arr, f) {
return Enum.reduce(arr, [], function(v, acm) {
if (!f(v)) {
acm.push(v);
}
return acm;
});
}
これでヨシ
ここまでやればほとんどforを見なくて済むはず?
eachSlice
これはニッチな例だけどあると便利なので実装したい。
配列を指定した個数単位の2次元配列にしてくれる関数。
[1,2,3,4,5,6]
[[1, 2], [3, 4], [5, 6]]
こういうのですね。
Enum.eachSlice = function(arr, amount) {
return Enum.reduce(arr, [], function(v, acm) {
if (acm.length == 0) {
var newAcm = [[]];
} else {
var newAcm = acm;
};
var last = newAcm.slice(-1)[0];
if (last.length < amount) {
last.push(v)
} else {
newAcm.push([v]);
}
return newAcm;
});
}
この関数、作ったはいいけど保守はしたくないなぁ...
compact
特定の条件で配列にnullが紛れ込むので排除したい。
これはrejectを使えば簡単に実現できそうだぞ!
Enum.compact = function(arr) {
return Enum.reject(arr, function(a) {
return a === null;
});
}
なんかJSのfalseの判定ってもっと簡潔に書けそうな気はするけどイラレスクリプト触ることがそんなに無いから許されたい。
include
指定した値が配列に含まれているか検証したいときに使います。
Enum.include = function(arr, v) {
var res = Enum.filter(arr, function(a) {
return a == v;
});
return res.length >= 1;
}
(Map)keys
これは配列操作ではないので番外的な感じですが、どうやらES3には Object.keys
が無いらしく {}
からキーを取得できないとわかった。
for in
というのを使うと取得できるっぽいのでこっちも実装しておく
var Map = {};
Map.keys = function(map) {
var keys = []
for (var key in map) {
keys.push(key);
};
return keys;
}
groupBy
Map.keysとincludeが実装できたのでgroupByを実装できます!
配列を指定した条件をキーにしたオブジェクトに変換してくれます。
[1,2,3,4,5]
// % 2の結果をキーにした場合
{
'0' => [2, 4],
'1' => [1, 3, 5]
}
Enum.groupBy = function(arr, f) {
return Enum.reduce(arr, [], function(v, acm) {
var newAcm = acm;
var key = f(v).toString();
if (Enum.include(Map.keys(acm), key)) {
newAcm[key].push(v);
} else {
newAcm[key] = [v];
};
return newAcm;
});
}
おわり
やってみた感想
普段お世話になっている関数群を再帰無しで実装してみたので親しみが増した。
これでforを使える言語ならある程度自由にEnumerableなコードを使えそうで安心した。
必要にかられて作っただけなので詳しい方おられたらここが変だよコメントいただけると嬉しいです。
Discussion