Cách thực hành
Tôi nghĩ thật sai lầm khi nói một triển khai cụ thể là "Đúng cách ™" nếu chỉ "đúng" ("đúng") trái ngược với giải pháp "sai". Giải pháp của Tomáš là một cải tiến rõ ràng so với so sánh mảng dựa trên chuỗi, nhưng điều đó không có nghĩa là nó "đúng" một cách khách quan. Điều gì là đúng ? Có phải là nhanh nhất? Nó có linh hoạt nhất không? Là nó dễ hiểu nhất? Đây có phải là cách nhanh nhất để gỡ lỗi? Nó sử dụng ít hoạt động nhất? Nó có bất kỳ tác dụng phụ? Không một giải pháp nào có thể có thứ tốt nhất trong tất cả mọi thứ.
Tomáš có thể nói giải pháp của anh ấy rất nhanh nhưng tôi cũng sẽ nói rằng nó rất phức tạp. Nó cố gắng trở thành một giải pháp tất cả trong một, hoạt động cho tất cả các mảng, lồng nhau hoặc không. Trên thực tế, nó thậm chí còn chấp nhận nhiều hơn là các mảng như một đầu vào và vẫn cố gắng đưa ra câu trả lời "hợp lệ".
Generics cung cấp tái sử dụng
Câu trả lời của tôi sẽ tiếp cận vấn đề khác nhau. Tôi sẽ bắt đầu với một arrayCompare
thủ tục chung chỉ liên quan đến việc bước qua các mảng. Từ đó, chúng tôi sẽ xây dựng chức năng so sánh cơ bản khác của chúng tôi như arrayEqual
và arrayDeepEqual
, vv
// 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)
Theo tôi, loại mã tốt nhất thậm chí không cần bình luận, và điều này cũng không ngoại lệ. Có quá ít xảy ra ở đây mà bạn có thể hiểu hành vi của thủ tục này mà hầu như không có nỗ lực nào cả. Chắc chắn, một số cú pháp ES6 có vẻ xa lạ với bạn bây giờ, nhưng đó chỉ là do ES6 còn khá mới.
Như kiểu gợi ý, arrayCompare
có chức năng so sánh f
, và hai mảng đầu vào, xs
và ys
. Đối với hầu hết các phần, tất cả những gì chúng ta làm là gọi f (x) (y)
cho từng phần tử trong mảng đầu vào. Chúng tôi trở lại một đầu false
nếu người dùng xác định f
lợi nhuận false
- nhờ &&
's đánh giá ngắn mạch. Vì vậy, có, điều này có nghĩa là bộ so sánh có thể dừng lặp lại sớm và ngăn chặn việc lặp qua phần còn lại của mảng đầu vào khi không cần thiết.
So sánh nghiêm ngặt
Tiếp theo, sử dụng arrayCompare
chức năng của chúng tôi , chúng tôi có thể dễ dàng tạo các chức năng khác mà chúng tôi có thể cần. Chúng tôi sẽ bắt đầu với tiểu 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
Đơn giản như thế. arrayEqual
có thể được định nghĩa với arrayCompare
và một hàm so sánh so sánh a
với b
việc sử dụng ===
(đối với đẳng thức nghiêm ngặt).
Lưu ý rằng chúng tôi cũng định nghĩa equal
là chức năng riêng của nó. Điều này nhấn mạnh vai trò của arrayCompare
một hàm bậc cao hơn để sử dụng bộ so sánh thứ tự đầu tiên của chúng tôi trong bối cảnh của một loại dữ liệu khác (Mảng).
So sánh lỏng lẻo
Chúng ta có thể dễ dàng xác định arrayLooseEqual
bằng cách sử dụng ==
thay thế. Bây giờ khi so sánh 1
(Số) để '1'
(String), kết quả sẽ là 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
So sánh sâu (đệ quy)
Bạn có thể nhận thấy rằng đây chỉ là so sánh nông. Chắc chắn giải pháp của Tomáš là "The Right Way ™" bởi vì nó ngầm ẩn sự so sánh sâu sắc, phải không?
Chà, arrayCompare
thủ tục của chúng tôi đủ linh hoạt để sử dụng theo cách làm cho một bài kiểm tra công bằng sâu sắc trở nên dễ dàng
// 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
Đơn giản như thế. Chúng tôi xây dựng một bộ so sánh sâu bằng cách sử dụng chức năng bậc cao khác . Lần này chúng tôi sẽ arrayCompare
sử dụng một bộ so sánh tùy chỉnh sẽ kiểm tra xem a
và b
có phải là mảng không. Nếu vậy, hãy áp dụng lại arrayDeepCompare
so sánh a
và so sánh với bộ so sánh b
do người dùng chỉ định ( f
). Điều này cho phép chúng tôi giữ hành vi so sánh sâu tách biệt với cách chúng tôi thực sự so sánh các yếu tố riêng lẻ. Ví dụ, như ví dụ trên cho thấy, chúng ta có thể so sánh sâu sử dụng equal
, looseEqual
hoặc bất kỳ so sánh khác mà chúng tôi thực hiện.
Vì arrayDeepCompare
bị chế giễu, chúng ta có thể áp dụng một phần như trong các ví dụ trước
// arrayDeepEqual :: [a] -> [a] -> Bool
const arrayDeepEqual =
arrayDeepCompare (equal)
// arrayDeepLooseEqual :: [a] -> [a] -> Bool
const arrayDeepLooseEqual =
arrayDeepCompare (looseEqual)
Đối với tôi, đây đã là một cải tiến rõ ràng so với giải pháp của Tomáš vì tôi rõ ràng có thể chọn một so sánh nông hoặc sâu cho các mảng của mình, khi cần.
So sánh đối tượng (ví dụ)
Bây giờ nếu bạn có một mảng các đối tượng hoặc một cái gì đó thì sao? Có lẽ bạn muốn coi các mảng đó là "bằng nhau" nếu mỗi đối tượng có cùng id
giá trị
// 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
Đơn giản như thế. Ở đây tôi đã sử dụng các đối tượng JS vanilla, nhưng loại so sánh này có thể hoạt động cho bất kỳ loại đối tượng nào ; thậm chí các đối tượng tùy chỉnh của bạn. Giải pháp của Tomáš sẽ cần phải được làm lại hoàn toàn để hỗ trợ loại thử nghiệm bình đẳng này
Mảng sâu với các đối tượng? Không thành vấn đề. Chúng tôi đã xây dựng các chức năng chung, linh hoạt cao, vì vậy chúng sẽ hoạt động trong nhiều trường hợp sử dụng khác nhau.
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
So sánh tùy tiện (ví dụ)
Hoặc nếu bạn muốn làm một số loại so sánh hoàn toàn tùy ý khác thì sao? Có lẽ tôi muốn biết nếu mỗi cái x
lớn hơn mỗi tên y
lửa
// 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
Càng đơn giản càng đẹp
Bạn có thể thấy chúng tôi thực sự đang làm nhiều hơn với ít mã hơn. Không có gì phức tạp về arrayCompare
bản thân và mỗi bộ so sánh tùy chỉnh chúng tôi đã thực hiện có một cách thực hiện rất đơn giản.
Để dễ dàng, chúng ta có thể xác định chính xác cách chúng ta muốn so sánh hai mảng - nông, sâu, nghiêm ngặt, lỏng lẻo, một số thuộc tính đối tượng hoặc một số tính toán tùy ý hoặc bất kỳ kết hợp nào của các mảng - tất cả đều sử dụng một thủ tục , arrayCompare
. Thậm chí có thể mơ lên một RegExp
so sánh! Tôi biết trẻ em thích những trò regexps như thế nào
Có phải là nhanh nhất? Không. Nhưng nó có lẽ không cần phải là một trong hai. Nếu tốc độ là chỉ số duy nhất được sử dụng để đo lường chất lượng mã của chúng tôi, thì rất nhiều mã thực sự tuyệt vời sẽ bị loại bỏ - Đó là lý do tại sao tôi gọi phương pháp này là Cách thực tế . Hoặc có thể để công bằng hơn, Một cách thực tế. Mô tả này phù hợp với câu trả lời này vì tôi không nói câu trả lời này chỉ thực tế so với một số câu trả lời khác; nó là khách quan đúng Chúng tôi đã đạt được mức độ thực tiễn cao với rất ít mã rất dễ lý do. Không có mã nào khác có thể nói chúng tôi chưa kiếm được mô tả này.
Điều đó làm cho nó là giải pháp "đúng" cho bạn? Đó là do bạn quyết định. Và không ai khác có thể làm điều đó cho bạn; chỉ có bạn biết nhu cầu của bạn là gì. Trong hầu hết các trường hợp, tôi đánh giá mã đơn giản, thực tế và linh hoạt hơn loại thông minh và nhanh chóng. Những gì bạn đánh giá có thể khác nhau, vì vậy hãy chọn những gì phù hợp với bạn.
Biên tập
Câu trả lời cũ của tôi tập trung hơn vào việc phân rã arrayEqual
thành các thủ tục nhỏ. Đây là một bài tập thú vị, nhưng không thực sự là cách tốt nhất (thực tế nhất) để tiếp cận vấn đề này. Nếu bạn quan tâm, bạn có thể xem lịch sử sửa đổi này.