实用方式
我认为,如果某个实现只是“正确的”(“正确的”),而不是“错误的”解决方案,那么说一个特定的实现就是“正确的方式”是错误的。Tomáš的解决方案是对基于字符串的数组比较的明显改进,但这并不意味着它客观上是“正确的”。反正什么是对的?是最快的吗?最灵活吗?这是最容易理解的吗?调试最快吗?它使用最少的操作吗?有副作用吗?没有任何一种解决方案可以拥有所有优点。
汤玛斯(Tomáš)可以说他的解决方案是快速的,但我也要说它不必要地复杂。它试图成为一种适用于所有数组(无论是否嵌套)的多合一解决方案。实际上,它不仅接受数组作为输入,而且仍然尝试给出“有效”答案。
泛型提供可重用性
我的回答将以不同的方式处理该问题。我将从通用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
,以及两个输入数组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
使用a ==
来轻松定义。现在,将1
(数字)与'1'
(字符串)进行比较时,结果将是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
深度比较(递归)
您可能已经注意到,这只是比较浅的比较。Tomáš的解决方案肯定是“ 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
and b
是数组。如果是这样,请arrayDeepCompare
以其他方式重新应用a
并b
与用户指定的比较器(f
)进行比较。这使我们能够将深度比较行为与实际比较各个元素的方式分开。即,像上面的例子所示,我们可以深刻的比较使用equal
,looseEqual
或其他任何比较,我们做。
因为arrayDeepCompare
是咖喱,所以我们也可以像前面的示例一样部分应用它
// arrayDeepEqual :: [a] -> [a] -> Bool
const arrayDeepEqual =
arrayDeepCompare (equal)
// arrayDeepLooseEqual :: [a] -> [a] -> Bool
const arrayDeepLooseEqual =
arrayDeepCompare (looseEqual)
对我来说,这已经是Tomáš解决方案的明显改进,因为我可以根据需要为数组明确选择浅表或深表比较。
对象比较(示例)
现在,如果您有一系列对象或东西呢?如果每个对象具有相同的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对象,但是这种类型的比较器可以用于任何对象类型。甚至您的自定义对象。托马斯(Tomáš)的解决方案需要完全重新设计以支持这种平等测试
带有对象的深层阵列?没问题 我们构建了高度通用的通用功能,因此它们可以在各种用例中使用。
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
本身并不复杂,我们制作的每个自定义比较器都有一个非常简单的实现。
轻松,我们可以定义正是我们希望的要比较两个数组-浅,深,严,活,一些对象属性,或一些任意的计算,或者它们的任意组合- 所有使用一个程序,arrayCompare
。甚至可以梦想一个RegExp
比较器!我知道孩子们如何爱那些正则表达式……
是最快的吗?不。但这可能也不需要。如果速度是用来衡量代码质量的唯一指标,那么很多非常好的代码就会被扔掉-这就是为什么我将这种方法称为“实用方法”。或者更公平地说,一种实用方式。此说明适用于此答案,因为我并不是说此答案仅与其他答案相比是实际的;客观上是正确的。我们已经以很少的代码(很容易推理)获得了高度的实用性。没有其他代码可以说我们还没有获得此描述。
这是否使其成为您的“正确”解决方案?由您决定。没有人能为您做到这一点;只有你知道你的需求是什么。在几乎所有情况下,我都认为简单,实用和通用的代码比聪明,快速的代码更有价值。您的价值可能会有所不同,因此请选择最适合您的。
编辑
我以前的答案更多地集中在分解arrayEqual
为小程序上。这是一个有趣的练习,但实际上并不是解决此问题的最佳(最实用)方法。如果您有兴趣,可以查看此修订历史记录。