Практичний шлях
Я думаю, що неправильно говорити, що конкретна реалізація є "Правильним шляхом ™", якщо вона є лише "правильною" ("правильною") на відміну від "неправильного" рішення. Рішення Томаша - це явне поліпшення порівняння порівняння масивів на основі рядків, але це не означає, що це об'єктивно "правильно". Що правильно таки ? Це найшвидше? Це найбільш гнучко? Це найлегше зрозуміти? Це найшвидше налагодити? Чи використовується найменше операцій? Чи є які-небудь побічні ефекти? Жодне рішення не може мати найкраще з усіх речей.
Томаш міг би сказати, що його рішення швидко, але я б також сказав, що це зайве складність. Він намагається бути рішенням "все в одному", яке працює для всіх масивів, вкладених чи ні. Насправді він навіть приймає більше, ніж просто масиви, як вхідні дані і все ще намагається дати "дійсну" відповідь.
Генерики пропонують повторне використання
Моя відповідь підійде до проблеми по-різному. Почну з загальної 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
використовуючи ==
натомість. Тепер, порівнюючи 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
Глибоке порівняння (рекурсивне)
Ви, напевно, помітили, що це лише неглибоке порівняння. Безумовно, рішення Томаша - «Правильний шлях ™», тому що це неявне глибоке порівняння, правда?
Ну і наша 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
, і кожен із створених нами на замовлення компараторів має дуже просту реалізацію.
З легкістю, ми можемо точно визначити , як ми хочемо , два масиви для порівняння - невеликий, глибокий, строгий, вільний, деякі властивості об'єкта, або деякі довільні обчислення, або будь-яка комбінація з них - все з допомогою однієї процедури , arrayCompare
. Можливо, навіть придумати RegExp
компаратор! Я знаю, як діти люблять ці виразки ...
Це найшвидше? Ні. Але це, мабуть, не потрібно. Якщо швидкість - єдиний показник, який використовується для вимірювання якості нашого коду, багато справді чудового коду буде відкинуто - саме тому я називаю такий підхід Практичним шляхом . А може бути більш справедливим, A Практичний підхід. Цей опис підходить для цієї відповіді, оскільки я не кажу, що ця відповідь є практичною лише порівняно з якоюсь іншою відповіддю; це об'єктивно вірно. Ми досягли високого ступеня практичності з дуже маленьким кодом, про що дуже легко міркувати. Жоден інший код не може сказати, що ми не заробили цей опис.
Це робить це "правильним" рішенням для вас? Це вам вирішити. І ніхто більше не може цього зробити для вас; тільки ти знаєш, які твої потреби. Майже у всіх випадках я ціную прямий, практичний та універсальний код за розумний та швидкий вид. Те, що ви цінуєте, може відрізнятися, тому виберіть те, що вам підходить.
Редагувати
Моя стара відповідь була більш зосереджена на розкладанні arrayEqual
на крихітні процедури. Це цікава вправа, але насправді не найкращий (найпрактичніший) спосіб підійти до цієї проблеми. Якщо вас цікавить, ви можете переглянути цю історію редагувань.