実用的な方法
特定の実装が「間違った」ソリューションとは対照的に「正しい」(「正しい」)だけである場合、「The Right Way™」であると言うのは間違っていると思います。トマーシュのソリューションは、文字列ベースの配列比較よりも明らかに改善されていますが、それが客観的に「正しい」ことを意味するわけではありません。とにかく何が正しいですか?最速ですか?最も柔軟性がありますか?理解するのが最も簡単ですか?デバッグが最速ですか?最小限の操作を使用していますか?副作用はありますか?1つのソリューションがすべてのものの中で最高のものを持つことはできません。
トマシュは彼の解決策は速いと言えるかもしれませんが、私はそれが不必要に複雑であるとも言います。ネストされているかどうかにかかわらず、すべての配列で機能するオールインワンソリューションを目指しています。実際、入力として配列だけでなく、それでも「有効な」答えを出そうとします。
ジェネリックは再利用性を提供します
私の答えは別の方法で問題に取り組みます。arrayCompare
まず、配列をステップ実行することのみに関係する一般的な手順から始めます。そこから、arrayEqual
およびarrayDeepEqual
などの他の基本的な比較関数を作成します
// arrayCompare :: (a -> a -> Bool) -> [a] -> [a] -> Bool
const arrayCompare = f => ([x,...xs]) => ([y,...ys]) =>
x === undefined && y === undefined
? true
: Boolean (f (x) (y)) && arrayCompare (f) (xs) (ys)
私の意見では、最良の種類のコードにはコメントも必要ありません。これも例外ではありません。ここで起こっていることはほとんどないので、ほとんど手間をかけずにこの手順の動作を理解できます。確かに、ES6の構文の一部は、今では見慣れないかもしれませんが、それは、ES6が比較的新しいためです。
タイプが示唆するように、arrayCompare
は比較関数f
、および2つの入力配列、xs
を受け取りますys
。ほとんどの場合、f (x) (y)
入力配列の各要素を呼び出すだけです。false
ユーザー定義のf
戻り値があれば、早期に戻りますfalse
-の&&
短絡評価のおかげです。つまり、これはコンパレータが反復を早期に停止し、不要な場合に残りの入力配列をループしないようにすることができることを意味します。
厳密な比較
次に、このarrayCompare
関数を使用して、必要な他の関数を簡単に作成できます。小学校から始めましょうarrayEqual
…
// equal :: a -> a -> Bool
const equal = x => y =>
x === y // notice: triple equal
// arrayEqual :: [a] -> [a] -> Bool
const arrayEqual =
arrayCompare (equal)
const xs = [1,2,3]
const ys = [1,2,3]
console.log (arrayEqual (xs) (ys)) //=> true
// (1 === 1) && (2 === 2) && (3 === 3) //=> true
const zs = ['1','2','3']
console.log (arrayEqual (xs) (zs)) //=> false
// (1 === '1') //=> false
そのような単純な。を使用して比較するコンパレーター関数でarrayEqual
定義できます(厳密な等価性のため)。arrayCompare
a
b
===
equal
独自の関数として定義することにも注意してください。これは、arrayCompare
別のデータ型(配列)のコンテキストで一次コンパレーターを利用するための高次関数としての役割を強調しています。
ゆるい比較
代わりにarrayLooseEqual
を使用して、簡単に定義でき==
ます。1
(Number)と'1'
(String)を比較すると、結果はtrue
…
// looseEqual :: a -> a -> Bool
const looseEqual = x => y =>
x == y // notice: double equal
// arrayLooseEqual :: [a] -> [a] -> Bool
const arrayLooseEqual =
arrayCompare (looseEqual)
const xs = [1,2,3]
const ys = ['1','2','3']
console.log (arrayLooseEqual (xs) (ys)) //=> true
// (1 == '1') && (2 == '2') && (3 == '3') //=> true
深い比較(再帰)
これは浅い比較だけであることに気づいたでしょう。きっとトマシュの解法は「The Right Way™」です。それは暗黙の深い比較を行うからですよね?
まあ私たちのarrayCompare
手順は、深い同等性テストを簡単にする方法で使用するのに十分な用途があります…
// isArray :: a -> Bool
const isArray =
Array.isArray
// arrayDeepCompare :: (a -> a -> Bool) -> [a] -> [a] -> Bool
const arrayDeepCompare = f =>
arrayCompare (a => b =>
isArray (a) && isArray (b)
? arrayDeepCompare (f) (a) (b)
: f (a) (b))
const xs = [1,[2,[3]]]
const ys = [1,[2,['3']]]
console.log (arrayDeepCompare (equal) (xs) (ys)) //=> false
// (1 === 1) && (2 === 2) && (3 === '3') //=> false
console.log (arrayDeepCompare (looseEqual) (xs) (ys)) //=> true
// (1 == 1) && (2 == 2) && (3 == '3') //=> true
そのような単純な。別の高次関数を使用してディープコンパレータを作成します。今回我々は、ラップしているarrayCompare
かどうかを確認するカスタムコンパレータ使用a
してb
配列されているが。もしそうであれば、再適用arrayDeepCompare
別段の比較a
とb
ユーザ指定のコンパレータ(へf
)。これにより、個々の要素を実際に比較する方法とは別に、深い比較動作を維持できます。つまり、上の例のように、我々は深く使用して比較することができequal
、looseEqual
あるいは我々が作る任意の他のコンパレータ。
のでarrayDeepCompare
カレーされ、我々は前の例で行ったように、我々は、部分的にもそれを適用することができます
// arrayDeepEqual :: [a] -> [a] -> Bool
const arrayDeepEqual =
arrayDeepCompare (equal)
// arrayDeepLooseEqual :: [a] -> [a] -> Bool
const arrayDeepLooseEqual =
arrayDeepCompare (looseEqual)
必要に応じて、配列の浅い比較または深い比較を明示的に選択できるため、これはトマシュのソリューションよりも明らかに改善されています。
オブジェクト比較(例)
オブジェクトの配列などがある場合はどうでしょうか?各オブジェクトのid
値が同じである場合、これらの配列を「等しい」と見なしたいと思うかもしれません…
// idEqual :: {id: Number} -> {id: Number} -> Bool
const idEqual = x => y =>
x.id !== undefined && x.id === y.id
// arrayIdEqual :: [a] -> [a] -> Bool
const arrayIdEqual =
arrayCompare (idEqual)
const xs = [{id:1}, {id:2}]
const ys = [{id:1}, {id:2}]
console.log (arrayIdEqual (xs) (ys)) //=> true
// (1 === 1) && (2 === 2) //=> true
const zs = [{id:1}, {id:6}]
console.log (arrayIdEqual (xs) (zs)) //=> false
// (1 === 1) && (2 === 6) //=> false
そのような単純な。ここではバニラJSオブジェクトを使用しましたが、このタイプのコンパレータはどのオブジェクトタイプでも機能します。カスタムオブジェクトも。この種の等価性テストをサポートするには、トマシュのソリューションを完全に作り直す必要があります
オブジェクトを含む深い配列?問題ない。汎用性の高い汎用関数を作成したため、さまざまなユースケースで機能します。
const xs = [{id:1}, [{id:2}]]
const ys = [{id:1}, [{id:2}]]
console.log (arrayCompare (idEqual) (xs) (ys)) //=> false
console.log (arrayDeepCompare (idEqual) (xs) (ys)) //=> true
任意の比較(例)
または、他の種類の完全に恣意的な比較を実行したい場合はどうなりますか?たぶん、それぞれx
がそれぞれより大きいかどうか知りたいのですがy
…
// gt :: Number -> Number -> Bool
const gt = x => y =>
x > y
// arrayGt :: [a] -> [a] -> Bool
const arrayGt = arrayCompare (gt)
const xs = [5,10,20]
const ys = [2,4,8]
console.log (arrayGt (xs) (ys)) //=> true
// (5 > 2) && (10 > 4) && (20 > 8) //=> true
const zs = [6,12,24]
console.log (arrayGt (xs) (zs)) //=> false
// (5 > 6) //=> false
少ないほうがいいですね
少ないコードで実際に多くのことを行っていることがわかります。arrayCompare
それ自体については何も複雑ではなく、私たちが作成した各カスタムコンパレータは非常に単純な実装を持っています。
簡単に、我々は、2つの配列を比較することのために私たちが望む正確にどのように定義することができます-浅い、深い、厳格な、緩い、一部のオブジェクトのプロパティ、またはいくつかの任意の計算、またはこれらの任意の組み合わせ- すべてを1つの手順を使用して、arrayCompare
。たぶん、RegExp
コンパレータを夢見てください!子供がこれらの正規表現をどのように愛しているか知っています…
最速ですか?いいえ。しかし、おそらくどちらかである必要はありません。コードの品質を測定するために使用される唯一の測定基準が速度である場合、非常に優れたコードの多くが破棄されます。そのため、このアプローチを実践的な方法と呼んでいます。それとも、より公正であると実用的な方法。この答えは他のいくつかの答えと比較して実用的であるとだけ言っているわけではないので、この説明はこの答えに適しています。それは客観的に真実です。推論が非常に簡単な非常に小さなコードで高度な実用性を達成しました。他のコードでは、この説明を得ていないと言うことはできません。
それはあなたにとって「正しい」解決策になりますか?それはあなたが決めることです。そして、他の誰もあなたのためにそれを行うことはできません。あなただけがあなたのニーズが何であるかを知っています。ほとんどすべての場合で、私は、賢くて速い種類よりも、単純で実用的で用途の広いコードを重視しています。何を評価するかは異なる可能性があるため、自分に合ったものを選択してください。
編集する
私の古い答えはarrayEqual
、小さな手順に分解することにもっと焦点を当てていました。これは興味深い演習ですが、この問題に取り組むための最良の(最も実用的な)方法ではありません。興味があれば、この変更履歴を見ることができます。