Cara Praktis
Saya pikir itu salah untuk mengatakan implementasi tertentu adalah "The Right Way ™" jika itu hanya "benar" ("benar") berbeda dengan solusi "salah". Solusi Tomáš adalah peningkatan yang jelas atas perbandingan array berbasis string, tetapi itu tidak berarti itu secara objektif "benar". Apa yang benar ? Apakah ini yang tercepat? Apakah ini yang paling fleksibel? Apakah ini yang termudah untuk dipahami? Apakah ini cara tercepat untuk melakukan debug? Apakah itu menggunakan operasi paling sedikit? Apakah ada efek sampingnya? Tidak ada satu solusi yang dapat memiliki yang terbaik dari semua hal.
Tomáš dapat mengatakan solusinya cepat tetapi saya juga akan mengatakan itu tidak perlu rumit. Itu mencoba untuk menjadi solusi all-in-one yang bekerja untuk semua array, bersarang atau tidak. Bahkan, ia menerima lebih dari sekadar array sebagai input dan masih berusaha memberikan jawaban yang "valid".
Generik menawarkan usabilitas
Jawaban saya akan mendekati masalah secara berbeda. Saya akan mulai dengan arrayCompare
prosedur generik yang hanya berkaitan dengan melangkah melalui array. Dari sana, kami akan membangun fungsi perbandingan dasar kami seperti arrayEqual
dan arrayDeepEqual
, dll
// 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)
Menurut pendapat saya, jenis kode terbaik bahkan tidak perlu komentar, dan ini tidak terkecuali. Ada begitu sedikit yang terjadi di sini sehingga Anda dapat memahami perilaku prosedur ini dengan hampir tidak ada usaha sama sekali. Tentu, beberapa sintaks ES6 mungkin tampak asing bagi Anda sekarang, tetapi itu hanya karena ES6 relatif baru.
Seperti yang disarankan oleh tipe, arrayCompare
dibutuhkan fungsi perbandingan f
,, dan dua larik input, xs
dan ys
. Untuk sebagian besar, yang kita lakukan hanyalah memanggil f (x) (y)
untuk setiap elemen dalam array input. Kami mengembalikan lebih awal false
jika pengguna yang ditentukan f
kembali false
- berkat &&
evaluasi hubung singkat. Jadi ya, ini berarti komparator dapat menghentikan iterasi lebih awal dan mencegah perulangan melalui sisa array input ketika tidak perlu.
Perbandingan yang ketat
Selanjutnya, menggunakan arrayCompare
fungsi kita, kita dapat dengan mudah membuat fungsi lain yang mungkin kita butuhkan. Kami akan mulai dengan 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
Sederhana seperti itu. arrayEqual
dapat didefinisikan dengan arrayCompare
dan fungsi pembanding yang membandingkan a
dengan b
menggunakan ===
(untuk kesetaraan yang ketat).
Perhatikan bahwa kami juga mendefinisikan equal
fungsinya sendiri. Ini menyoroti peran arrayCompare
sebagai fungsi tingkat tinggi untuk memanfaatkan pembanding orde pertama kami dalam konteks tipe data lain (Array).
Perbandingan longgar
Kita bisa dengan mudah didefinisikan arrayLooseEqual
menggunakan ==
gantinya. Sekarang ketika membandingkan 1
(Nomor) ke '1'
(String), hasilnya adalah 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
Perbandingan mendalam (rekursif)
Anda mungkin telah memperhatikan bahwa ini hanya perbandingan dangkal. Tentunya solusi Tomáš adalah "The Right Way ™" karena ia melakukan perbandingan mendalam secara implisit, bukan?
Nah arrayCompare
prosedur kami cukup fleksibel untuk digunakan dengan cara yang membuat tes kesetaraan yang mendalam menjadi mudah ...
// 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
Sederhana seperti itu. Kami membangun komparator dalam menggunakan fungsi tingkat tinggi lainnya . Kali ini kami membungkus arrayCompare
menggunakan pembanding khusus yang akan memeriksa apakah a
dan b
array. Jika demikian, permohonan kembali arrayDeepCompare
membandingkan a
dan b
ke pembanding yang ditentukan pengguna ( f
). Ini memungkinkan kita untuk memisahkan perilaku perbandingan mendalam dari bagaimana kita benar-benar membandingkan unsur-unsur individu. Yaitu, seperti contoh di atas menunjukkan, kita bisa membandingkan menggunakan equal
,looseEqual
, atau pembanding lain yang kami buat.
Karena arrayDeepCompare
sudah dikeringkan, kita dapat menerapkannya sebagian seperti yang kita lakukan pada contoh sebelumnya juga
// arrayDeepEqual :: [a] -> [a] -> Bool
const arrayDeepEqual =
arrayDeepCompare (equal)
// arrayDeepLooseEqual :: [a] -> [a] -> Bool
const arrayDeepLooseEqual =
arrayDeepCompare (looseEqual)
Bagi saya, ini sudah merupakan peningkatan yang jelas atas solusi Tomáš karena saya dapat secara eksplisit memilih perbandingan yang dangkal atau dalam untuk array saya, sesuai kebutuhan.
Perbandingan objek (contoh)
Sekarang bagaimana jika Anda memiliki array benda atau sesuatu? Mungkin Anda ingin menganggap array itu sebagai "sama" jika setiap objek memiliki nilai yang sama 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
Sederhana seperti itu. Di sini saya telah menggunakan objek vanilla JS, tetapi jenis pembanding ini dapat bekerja untuk semua jenis objek; bahkan objek khusus Anda. Solusi Tomáš perlu dikerjakan ulang sepenuhnya untuk mendukung tes kesetaraan semacam ini
Array dalam dengan objek? Bukan masalah. Kami membangun fungsi generik yang sangat fleksibel, sehingga mereka akan bekerja dalam berbagai macam kasus penggunaan.
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
Perbandingan sewenang-wenang (contoh)
Atau bagaimana jika Anda ingin melakukan semacam perbandingan yang sepenuhnya sewenang-wenang? Mungkin saya ingin tahu apakah masing x
- masing lebih besar dari masing-masing 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
Kurang itu lebih
Anda dapat melihat kami sebenarnya melakukan lebih banyak dengan lebih sedikit kode. Tidak ada yang rumit tentang ituarrayCompare
dirinya sendiri dan masing-masing pembanding khusus yang kami buat memiliki implementasi yang sangat sederhana.
Dengan mudah, kita dapat mendefinisikan persis bagaimana kita ingin untuk dua array untuk dibandingkan - dangkal, dalam, ketat, longgar, beberapa objek properti, atau beberapa perhitungan sewenang-wenang, atau kombinasi dari ini - semua menggunakan satu prosedur , arrayCompare
. Mungkin bahkan bermimpi aRegExp
pembanding! Saya tahu bagaimana anak-anak menyukai regexps itu ...
Apakah ini yang tercepat? Nggak. Tapi mungkin juga tidak perlu. Jika kecepatan adalah satu-satunya metrik yang digunakan untuk mengukur kualitas kode kami, banyak kode yang benar-benar hebat akan dibuang - Itulah sebabnya saya menyebut pendekatan ini dengan Cara Praktis . Atau mungkin lebih adil, Cara Praktis. Deskripsi ini cocok untuk jawaban ini karena saya tidak mengatakan jawaban ini hanya praktis dibandingkan dengan beberapa jawaban lainnya; itu benar secara objektif. Kami telah mencapai tingkat kepraktisan yang tinggi dengan kode yang sangat sedikit yang sangat mudah untuk dipikirkan. Tidak ada kode lain yang dapat mengatakan bahwa kami belum mendapatkan deskripsi ini.
Apakah itu menjadikannya solusi "tepat" untuk Anda? Terserah Anda yang memutuskan. Dan tidak ada orang lain yang bisa melakukannya untuk Anda; hanya Anda yang tahu apa kebutuhan Anda. Dalam hampir semua kasus, saya menghargai kode yang mudah, praktis, dan serbaguna daripada jenis yang pintar dan cepat. Apa yang Anda nilai mungkin berbeda, jadi pilihlah yang cocok untuk Anda.
Edit
Jawaban lama saya lebih fokus pada penguraian arrayEqual
menjadi prosedur kecil. Ini latihan yang menarik, tetapi bukan cara terbaik (paling praktis) untuk mendekati masalah ini. Jika Anda tertarik, Anda dapat melihat riwayat revisi ini.