reduceが行う最大のことは、配列から値を取得し、配列の各要素に対してreducer関数を実行することです。
は同等のものを開きます:
// 理解しやすくするために、以下のように追加する。initValue
[x1, x2, x3].reduce(f,initValue)
// f少なくとも2つのパラメータ
f(f(f(initValue,x1), x2), x3);
REDUCEを書く核心は実ははっきりしています:
- initValue
- fは常に2つの引数accとitemを持っています。
これでほとんどの問題は解決し、初心者はREDUCEの書き方がいかに簡単かを知ることができるでしょう。
以下は、応用シナリオと良い練習の例です。
配列のすべての値を累積
const sumFn = (acc, item) => acc + item;
const sum = [1, 2, 3, 4].reduce(sumFn, 0);
// 6
console.log(sum);
オブジェクト配列の値を累積
const sumFn = (acc, item) => acc + item.x;
const sum = [{ x: 1 }, { x: 2 }, { x: 3 }, { x: 4 }].reduce(sumFn, 0);
// 6
console.log(sum);
低次元配列、2Dから1Dへ
// pushの代わりにconcatを使う理由は、concatはマージされた配列を返すのに対し、pushは配列の長さを返すからだ。
const flat = (acc, item) => acc.concat(item);
const res = [
[0, 1],
[2, 3],
[4, 5]
].reduce(flat, []);
// [0, 1, 2, 3, 4, 5]
console.log(res);
const flat = (acc, item) => acc.concat(item);
const res = [ 0, 1, [2, 3, [4, 5]] ].reduce(flat, []);
// [0, 1, 2, 3, 4, 5]
console.log(res)
// ここにアップデートがある。少し抽象化された関数で、現在のアイテムが配列であればフラット化し、そうでなければ連結する。
const flatten = arr =>
arr.reduce((a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []);
const names = ["Alice", "Bob", "Tiff", "Bruce", "Alice"];
const getCount = (obj, key) => {
key in obj || (obj[key] = 0);
obj[key]++;
return obj;
};
const res = names.reduce(getCount, {});
// { 'Alice': 2, 'Bob': 1, 'Tiff': 1, 'Bruce': 1 }
console.log(res);
const people = [
{ id: 1, name: "Alice", age: 21 },
{ id: 2, name: "Max", age: 20 },
{ id: 3, name: "Jane", age: 20 }
];
const getType = (obj, item) => {
const key = item.age;
key in obj || (obj[key] = []);
obj[key].push(item);
return obj;
};
const res = people.reduce(getType, {});
// { 20: [ { name: 'Max', age: 20 }, { name: 'Jane', age: 20 } ], 21: [{ name: 'Alice', age: 21 }] }
console.log(res);
任意のフィールドを分類するために、ここで少しアップグレードすることができます。
const classifyArray = (arr, keyClassified) => {
const getType = (obj, item) => {
const key = item[keyClassified];
key in obj || (obj[key] = []);
obj[key].push(item);
return obj;
};
return arr.reduce(getType, {});
};
const people = [
{ id: 1, name: "Alice", age: 21, gender: 0 },
{ id: 2, name: "Max", age: 20, gender: 1 },
{ id: 3, name: "Jane", age: 20, gender: 0 }
];
// { '0': [ { id: 1, name: 'Alice', age: 21, gender: 0 }, { id: 3, name: 'Jane', age: 20, gender: 0 } ], '1': [ { id: 2, name: 'Max', age: 20, gender: 1 } ] }
console.log(classifyArray(people, "gender"));
拡張演算子によるオブジェクト配列の配列処理
// すべての本をマージする
var friends = [
{
name: "Anna",
books: ["Bible", "Harry Potter"],
age: 21
},
{
name: "Bob",
books: ["War and peace", "Romeo and Juliet"],
age: 26
},
{
name: "Alice",
books: ["The Lord of the Rings", "The Shining"],
age: 18
}
];
const sumArr = (oldArr, item) => {
return [...oldArr, ...item.books];
};
const res = friends.reduce(sumArr, []);
// [ 'Bible', 'Harry Potter', 'War and peace', 'Romeo and Juliet', 'The Lord of the Rings', 'The Shining' ]
console.log(res);
var myArray = ["a", "b", "a", "b", "c", "e", "e", "c", "d", "d", "d", "d"];
const addArr = (acc, item) => (acc.includes(item) ? acc : [...acc, item]);
const res = myArray.reduce(addArr, []);
// [ 'a', 'b', 'c', 'e', 'd' ]
console.log(res);
もちろん、もっと簡単な方法はあります。
const unique = arr => Array.from(new Set(arr));
// [ 'a', 'b', 'c', 'e', 'd' ]
console.log(
unique(["a", "b", "a", "b", "c", "e", "e", "c", "d", "d", "d", "d"])
);
// promise function 1
function p1(a) {
return new Promise(resolve => {
resolve(a * 5);
});
}
// MapReduceだ!これは普通の機能だよ!
function f(a) {
return a * 2;
}
// promise function 2
function p2(a) {
return new Promise(resolve => {
resolve(a - 2);
});
}
const arr = [p1, f, p2];
const runOrderly = (acc, item) => acc.then(item);
const res = arr.reduce(runOrderly, Promise.resolve(10));
// 98
console.log(res);
汎用の逐次実行関数をもう一度抽象的に書いてください。
const runOrderly = (arr, initValue) =>
arr.reduce((acc, item) => acc.then(item), Promise.resolve(initValue));
例として、組み合わせ関数とは何かを見てみましょう。
const sum = (a, b) => a + b;
const len = str => str.length;
const addCurrency = str => "$" + str;
// 今、私はまず2つの文字の和を求め、次に長さを求め、次に$これは新しい関数で、既知の関数を組み合わせたものである。
const newFn = (a, b) => addCurrency(len(sum(a, b)));
console.log(newFn("xyz", "abc"));
compose(addCurrency,len,sum)
しかし、上記のようにnewFnを地獄のようにネストして生成する代わりに、できれば、ここではreduceRightを試すことができます。
// compose(addCurrency,len,sum)
const compose = (...fns) => {
return (...args) => {
// ここで、最後の関数は他の関数と違ってパラメータが2つあるので、他の関数がすべてfn(acc)
const lastFn = fns.pop();
let initValue = lastFn(...args);
const f = (acc, fn) => fn(acc);
return fns.reduceRight(f, initValue);
};
};
const newFn = compose(addCurrency, len, sum);
console.log(newFn("xyz", "abc"));
// 簡略化する
const compose = (...fns) => (...args) =>
fns.reduceRight((acc, fn) => fn(acc), fns.pop()(...args));
もちろん、思考がもう少しパワフルであれば、REDUCEを使うこともできます。
// compose(addCurrency,len,sum)
const compose = (...fns) =>
fns.reduce((acc, cur) => (...args) => acc(cur(...args)));
これは考えにくい方法ですが、lenとsumの2つの関数だけを組み合わせたとします。
- compose
(...args) => len(sum(...args))
- リデュースに相当するのは
(...args)=>[len,sum].reduce((acc,curFn)=>(...args) => acc(curFn(...args)))
- len,sum]を他の配列に置き換えてください。
- 今度自分で書くかどうかはわからないけど、たぶんReduceRightにすると思います。
リデュースの落とし穴
- 空の配列は reduce
- reduce(f) に initValue がない場合、f の最初の引数は arr[0] arr[1] 1 arr
- fには少なくとも2つの引数が必要です。
arr[0] arr[1] 1 arr
より読みやすくするために、initValueを渡すことで、各操作に一貫性を持たせ、エラーを少なくすることができます。この記事でも