lodashパーティション方式の再作成


Lodash _.partition 関数は、配列を 2 つのグループに分割します.1 つは指定された条件を満たしている項目で満たされ、もう 1 つのグループは満たされていない項目で満たされています.

このブログ記事の目的は、パーティションの方法を複製することですが、いくつかの変更を加えて、いくつかの追加機能を追加することです.概念は同じままですが、1 つの述語を取る代わりに、関数は述語の配列 (パーティション関数) を取り、それらに基づいて与えられた配列を分割することができます.

Typescript の関数シグネチャは次のようになります

type PartitionWith = <T>(items: T[], predicates: ((item: T) => boolean)[]): T[][]


使用例は、配列を 2 つの配列に分割することです.一方には 5 より大きい数値が含まれ、もう一方には 5 以下の項目が含まれます.

const array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const isLessOrEqualThanFive = (number: number) => number <= 5; 
const isGreaterThanFive = (number) => number > 5;

const results = partitionWith(array, [isLessOrEqualThanFive, isGreaterThanFive ]); 

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


パーティション カウントが predicates 配列の長さに等しいことがわかります.最初のアサーションを記述し、それを通過させるコードを実装しましょう.

it('creates an array of partitions with a length that is equal to the predicates array length', () => {
        const predicateOne = (n: number) => n < 5; 
        const predicateTwo = (n: number) => n >= 5; 

        const array = [1, 2, 4, 5, 6]; 

        const results = partitionWith(array, [predicateOne, predicateTwo]); 

        expect(results.length).toBe(2); 
})


2 つの述語を渡すことは、結果の配列にも 2 つのパーティションが含まれている必要があることを意味します.

const partitionWith: PartitionWith = <T>(items: T[], predicates: ((item: T) => boolean)[]) => {
    const results: T[][] = [...Array(predicates.length)].map(x => []); 

    return results; 
}


この関数は、述語配列の長さと同じ長さの配列の配列を作成します.

次のステップは、ロジックを適用する述語を実装することです.アイデアは、述語が項目に対して true を返すたびに、後者がその述語インデックスでパーティション配列に追加されるということです.

述語インデックスを見つけるには、指定された条件を満たす最初のアイテムのインデックスを返す .findIndex 関数を使用するか、何も見つからない場合は -1 を返します.

const predicateIndex = predicates.findIndex(predicate => predicate(item));


機能を実装する前にテストを書きましょう.

it('create partitions based on the provided predicates', () => {
        const arrayToPartition = [0, 1, '1', 2, 3, 4, '12', 5, 6, 7, 8, 9, , '11', 10]; 

        const isLessThanFive = (maybeNumber: number | string) => typeof maybeNumber === 'number' && maybeNumber < 5; 
        const isGreaterOrEqualThanFive = (maybeNumber: number | string) => typeof maybeNumber === 'number' &&  maybeNumber >= 5;
        const isString = (maybeString: number | string) => typeof maybeString === 'string';  

        const results = partitionWith(arrayToPartition, [isLessThanFive, isGreaterOrEqualThanFive, isString]); 

        expect(results).toEqual([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9, 10], ['1', '12', '11']]); 
    });



type PartitionWith =  <T>(items: T[], predicates: ((item: T) => boolean)[]) => T[][];
export const partitionWith: PartitionWith = <T>(items: T[], predicates: ((item: T) => boolean)[]) => {
    const results: T[][] = [...Array(predicates.length)].map(x => []); 

    items.forEach((item) => {
        const predicateIndex = predicates.findIndex(predicate => predicate(item)); 

        if(predicateIndex !== -1) {
            results[predicateIndex].push(item); 
        }
    })

    return results; 
}


前述のように、要素ごとに、それが満たす述語を見つけようとします.見つかった場合は、results[predicateIndex].push(item); を使用して対応する述語インデックスに追加します.

ソリューションは、条件を満たさないすべてのアイテムを無視するようになりました.ただし、元の lodash _.partition 関数は配列を 2 つのグループに分割します.一方には条件を満たす要素が含まれ、もう一方には満たされない要素が含まれます.

それでは実装してみましょうが、まずはいつものようにロジックを実装する前にテストを書きます.

it('returns an extra array of items that did not satisfy any condition', () => {
        const items = [0, 1, '1', 2, 3, 4, '12', 5, 6, 7, 8, 9, , '11', 10]; 

        const isLessThanFive = (maybeNumber: number | string) => typeof maybeNumber === 'number' && maybeNumber < 5; 
        const isGreaterOrEqualThanFive = (maybeNumber: number | string) => typeof maybeNumber === 'number' &&  maybeNumber >= 5;

        const results = partitionWith(items, [isLessThanFive, isGreaterOrEqualThanFive]); 
        expect(results).toEqual([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9, 10], ['1', '12', '11']])

    })


ここには 2 つの条件があります.数値であり、5 未満または 5 を超えるアイテムのみを取得します.残りのアイテムは、パーティション配列の末尾にある配列に追加する必要があります.最初に、偽のアイテムを含むこの配列を結果配列に追加しましょう.

results.push([])


項目が指定された述語の少なくとも 1 つを満たさない場合は常に、インデックスが結果配列の最後にある配列に追加されます.アルゴリズムは次のようになります.以前のテストも適切にリファクタリングする必要があります.

export const partitionWith: PartitionWith = <T>(items: T[], predicates: ((item: T) => boolean)[]) => {
    const results: T[][] = [...Array(predicates.length)].map(x => []); 

    results.push([])

    items.forEach((item) => {
        const predicateIndex = predicates.findIndex(predicate => predicate(item)); 

        if(predicateIndex !== -1) {
            results[predicateIndex].push(item); 
        } else {
            const falsyResultsArrayIndex = predicates.length; 

            results[falsyResultsArrayIndex].push(item);
        }
    })

    return results; 
}


結果配列の末尾に偽の項目配列が追加されるため、そのインデックスは predicates.length になります.

これで、partitionWith 関数は、述語が 1 つだけ提供された場合、lodash の partition とまったく同じように動作し、偽の要素を結果配列の末尾にある別の配列に保持します.

テストスイートを含むフルバージョンはここにあります

https://codesandbox.io/s/awesome-almeida-b7hyp?file=/src/index.ts

詳細については、Twitter でフォローしてください