10
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ES6のconstを使い倒すレシピ4 - コレクション一発代入/一発返却編 〜 JSおくのほそ道 #037

Last updated at Posted at 2015-12-28

こんにちは、ほそ道です。
ここまでシリーズでconstで宣言した変数(変数より値の名前といったほうが個人的にはしっくり来ますが)の状態管理方法についてやってきました。

今回は反復操作を行うにあたってやむを得ずletを使うことになってしまうようなケースに対して、あくまでconst宣言した値に一発代入するようなレシピを作っていきたいと思います。

※オブジェクトや配列、MapSetに関してはconst宣言したものであっても子要素の追加/変更が行えますが今回の趣旨として一発変換・一発代入でありますので不変性の担保については過去編参照でおねがいします。

全体の目次はこちら

基本編・Array

Array.filter

まずはこんなケースからやってみます。
配列の中から偶数だけを取り出して新しい配列を生成する処理です.
下記のbeforeケースでは反復と条件分岐の結果を配列に詰めて返すために
反復インデックスiと返却配列evensをletで宣言しております。

before
const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let i = 0, evens = [];

for (; i < nums.length; i++) {
  if (nums[i] % 2 === 0) {
    evens.push(nums[i]);
  }
}

console.log(evens);   // [ 2, 4, 6, 8, 10 ]

これをArrayのfilterを使って下記のようにconstを使って一発で偶数配列を返します。
filterは引数に、「配列要素を一つ引数に取る関数」を受け取りtrueを返した場合の配列要素のみで新しい配列を作って返します。

after
const evens = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].filter(num => num % 2 === 0);

console.log(evens);   // [ 2, 4, 6, 8, 10 ]

こんな感じで幾つかのケースの一発代入レシピを作っていきたいと思います。

Array.map

よく使うものとしてmapメソッドも挙げておきます。
今回はfilterと、このmapを中心に据えていこうかなと思います。

const array = [1, 2, 3, 4, 5].map(elm => elm * 100);

console.log(array);    // [ 100, 200, 300, 400, 500 ]

filterは配列要素の絞込でしたが、こちらは各要素に対する変換となります。

応用的な考え方

組み合わせ

mapfilterも新しい配列を作って返すので、メソッドチェーンでつないで関数を生成することも可能です。

const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].filter(num => num % 2 === 0).map(num => num * 100);

console.log(array);    // [ 200, 400, 600, 800, 1000 ]

下記のようにして、値の検索を行うこともできます。

const has5 = [1, 3, 5, 6, 9].filter(num => num === 5).length > 0

console.log(has5);  // true

関数化

ちょっとだけ目線を変えて、filtermapを使って配列を返す関数を作ります。
よりシンプルな関数にすることができると思います。

const getEvens = array => array.filter(num => num % 2 === 0);

console.log(getEvens([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]));    // [ 2, 4, 6, 8, 10 ]

値の検索処理も関数化することで汎化できます。

const has = (array, value) => array.filter(num => num === value).length > 0

console.log(has([1, 3, 5, 6, 9], 5));  // true
console.log(has([1, 3, 5, 6, 9], 2));  // false

Map

ES6で追加されたMapもコレクションとして見ておきます。
Mapfiltermapメソッドを持っていないということが要注意です。

const map = new Map([['a', 1], ['b', 2], ['c', 3]]);

console.log(map.filter);  // undefined
console.log(map.map);     // undefined

ただし、MapforEachメソッドなら持っていますので下記のように書くことはできます。

const map = new Map([['a', 1], ['b', 2], ['c', 3]]);

const map2 = new Map();

map.forEach((v, k) => map2.set(k, v * 100));

console.log(map2);  // Map { 'a' => 100, 'b' => 200, 'c' => 300 }

上記のmap2に関してはconstで宣言はしているものの、あとから要素を追加しているので不変な扱い方はできていないところが微妙に感じるところです。

なので、その微妙さは下記のように解決してみます。

const map = new Map([['a', 1], ['b', 2], ['c', 3]]);

const map2 = new Map(Array.from(map).map(kv => [kv[0], kv[1] * 100]));

console.log(map2);  // Map { 'a' => 100, 'b' => 200, 'c' => 300 }

Array.fromをMapにカマすと[[key, value], [key, value]]な形式の配列にしてくれるのでmapやらfilterやらを好きに使える、と言った感じです。
この形式の配列はそのままMapのコンストラクタ引数になるので、変換してnew Mapにくわせてやれば良いという感じです。

オブジェクトリテラル生成オブジェクト

オブジェクトリテラルで作ったオブジェクトはMapと同じように変換していきたいと思います。
こちらはlodashが提供してくれるmapValues関数一発で対応します。

const _ = require('lodash');

const obj = {a: 1, b: 2, c: 3};

const obj2 = _.mapValues(obj, v => v * 100);

console.log(obj2);  // { a: 100, b: 200, c: 300 }

プロパティ名の方を変換したい場合はmapKeysを使って変換します。

const _ = require('lodash');

const obj = {a: 1, b: 2, c: 3};

const obj2 = _.mapKeys(obj, (v, k) => k + k + k);

console.log(obj2);  // { aaa: 1, bbb: 2, ccc: 3 }

immutable.js

前回も触れましたが、immutable.jsを使うことでconst定義したオブジェクトを不変なものとして扱えます。
基本的に配列はimmutable.ListMapimmutable.Map、オブジェクトはimmutable.Recordという読み替えで扱っていけば良いと思います。
で、今回の一発変換・一発代入レシピとしてはimmutable.jsが「ならではのもの」として持っている便利なメソッドにフォーカスを当てたいと思います。

immutable.Map.map

まず、先ほどMapのところでおこなった変換を、そのまんまimmutable.Mapに当てはめてみます。

const I = require('immutable');

const map = I.Map([['a', 1], ['b', 2], ['c', 3]]);

const map2 = I.Map(Array.from(map).map(kv => [kv[0], kv[1] * 100]));

console.log(map2);  // Map { "a": 100, "b": 200, "c": 300 }

これがものの見事に動きます。すごいなー。
で、もうちょっとシンプルにならないか、ということでimmutable.Mapmapメソッドなどの処理を持っていてくれてますので、そちらを使ってみます。

const I = require('immutable');

const map = I.Map([['a', 1], ['b', 2], ['c', 3]]);

const map2 = map.map(v => v * 100);

console.log(map2);  // Map { "a": 100, "b": 200, "c": 300 }

シンプルな感じにすることができました!
引数の関数は第二引数でkeyを受け取ることもできますが最終的にreturnする値はimmutable.Mapのvalueになるという感じです。

immutable.List.flatMap

例えば学校の時間割を曜日とn時間目で表現します。
[["月", 1], ["月", 2]...["金", 5]]のような感じにしたいなーと思った時単純にmapメソッドを重ねると下記のようになってしまいます。

const I = require('immutable');

const daysOfWeek = I.List(['', '', '', '', '']);

const lessons = I.List([1, 2, 3, 4, 5]);

const lessonsOfWeek = daysOfWeek.map(dayOfWeek => lessons.map(lesson => I.List([dayOfWeek, lesson])));

console.log(lessonsOfWeek);  // List [ List [ List [ "月", 1 ], List [ "月", 2 ] ... , List [ List [ "火", 1 ] ... , List [ "金", 5 ] ] ]

Listが三段重ねになって、曜日と曜日の間に間に余計なListができてしまいます。
こんな時にArrayは持っていないメソッドであるimmutable.List.flatMapを使ってみます。

const I = require('immutable');

const daysOfWeek = I.List(['', '', '', '', '']);

const lessons = I.List([1, 2, 3, 4, 5]);

const lessonsOfWeek = daysOfWeek.flatMap(dayOfWeek => lessons.map(lesson => I.List([dayOfWeek, lesson])));

console.log(lessonsOfWeek);  // List [ List [ "月", 1 ], List [ "月", 2 ] ... , List [ "火", 1 ] ... , List [ "金", 5 ] ]

一発で望む形のListを得ることができました!
こんな感じでimmutable.jsにはビルトインメソッドにはない強力な道具が色々搭載されています。
大概の望む形は一発で取得できると思いますので大変気に入っています。

今回は以上です。

10
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?