วิธีการปฏิบัติ
ฉันคิดว่ามันผิดที่จะบอกว่าการใช้งานเฉพาะอย่างนั้นคือ "The Right Way ™" ถ้ามันเป็น "ถูกต้อง" ("ถูกต้อง") ตรงกันข้ามกับวิธีแก้ปัญหา "ผิด" โซลูชันของTomášเป็นการปรับปรุงที่ชัดเจนเกี่ยวกับการเปรียบเทียบอาเรย์แบบสตริง แต่ไม่ได้หมายความว่ามัน "ถูกต้อง" อย่างเป็นกลาง อะไรคือสิ่งที่ถูกต้องหรือไม่? มันเร็วที่สุด? มันยืดหยุ่นที่สุดหรือไม่? มันง่ายที่สุดที่จะเข้าใจหรือไม่? การดีบักเร็วที่สุดหรือไม่ มันใช้การดำเนินงานน้อยที่สุดหรือไม่? มันมีผลข้างเคียงหรือไม่? ไม่มีใครสามารถแก้ไขสิ่งที่ดีที่สุดในทุกสิ่ง
โทมัสบอกได้ว่าวิธีแก้ปัญหาของเขานั้นเร็ว แต่ฉันก็บอกว่ามันซับซ้อนโดยไม่จำเป็น มันพยายามที่จะเป็นโซลูชั่นแบบ all-in-one ที่ใช้งานได้กับอาร์เรย์ทั้งหมดซ้อนกันหรือไม่ ในความเป็นจริงมันยอมรับมากกว่าเพียงแค่อาร์เรย์เป็นอินพุตและยังคงพยายามที่จะให้คำตอบ "ถูกต้อง"
ยาสามัญสามารถใช้ซ้ำได้
คำตอบของฉันจะเข้าหาปัญหาแตกต่างกัน ฉันจะเริ่มต้นด้วย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
เป็นฟังก์ชั่นที่มีลำดับสูงกว่าเพื่อใช้ตัวเปรียบเทียบลำดับแรกของเราในบริบทของชนิดข้อมูลอื่น (Array)
การเปรียบเทียบแบบหลวม
เราสามารถกำหนดได้อย่างง่ายดายเพียงแค่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
การเปรียบเทียบแบบลึก (แบบเรียกซ้ำ)
คุณอาจสังเกตเห็นว่านี่เป็นเพียงการเปรียบเทียบที่ตื้นเท่านั้น วิธีแก้ปัญหาของ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
และb
เป็นอาร์เรย์หรือไม่ หากเป็นเช่นนั้นให้ใช้การarrayDeepCompare
เปรียบเทียบa
และการเปรียบเทียบb
ที่ผู้ใช้ระบุ ( f
) อีกครั้ง สิ่งนี้ช่วยให้เราแยกพฤติกรรมการเปรียบเทียบอย่างลึกซึ้งออกจากวิธีการเปรียบเทียบองค์ประกอบแต่ละอย่างจริง คือเหมือนตัวอย่างข้างต้นแสดงให้เห็นว่าเราสามารถเปรียบเทียบลึกใช้equal
, looseEqual
หรือเปรียบเทียบอื่น ๆ ที่เราทำ
เนื่องจากarrayDeepCompare
curried เราสามารถนำบางส่วนมาใช้ได้เหมือนในตัวอย่างก่อนหน้านี้เช่นกัน
// 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
เปรียบเทียบ! ฉันรู้ว่าเด็ก ๆ รัก regexps เหล่านั้นอย่างไร
มันเร็วที่สุด? Nope แต่มันอาจไม่จำเป็นต้องเป็นอย่างใดอย่างหนึ่ง ถ้าความเร็วที่ใช้ในการวัดคุณภาพของรหัสของเราตัวชี้วัดเท่านั้นจำนวนมากของรหัสที่ดีจริงๆจะได้โยนไป - นั่นคือเหตุผลที่ผมเรียกวิธีการนี้ในทางปฏิบัติทาง หรืออาจจะยุติธรรมมากกว่านี้เป็นวิธีปฏิบัติ คำอธิบายนี้เหมาะสำหรับคำตอบนี้เพราะฉันไม่ได้กำลังบอกว่าคำตอบนี้ใช้ได้เฉพาะเมื่อเปรียบเทียบกับคำตอบอื่น ๆ มันเป็นความจริงอย่างเป็นกลาง เราได้รับการปฏิบัติจริงในระดับสูงโดยมีรหัสน้อยมากซึ่งง่ายต่อการให้เหตุผล ไม่มีรหัสอื่นที่สามารถบอกได้ว่าเราไม่ได้รับคำอธิบายนี้
นั่นทำให้โซลูชัน "ถูกต้อง" สำหรับคุณหรือไม่? ขึ้นอยู่กับคุณที่จะตัดสินใจ และไม่มีใครสามารถทำเพื่อคุณได้ มีเพียงคุณเท่านั้นที่รู้ว่าความต้องการของคุณคืออะไร ในเกือบทุกกรณีฉันให้ความสำคัญกับรหัสตรงไปตรงมาการใช้งานและเอนกประสงค์มากกว่าความฉลาดและความรวดเร็ว สิ่งที่คุณให้คุณค่าอาจแตกต่างกันดังนั้นเลือกสิ่งที่เหมาะกับคุณ
แก้ไข
คำตอบเก่าของฉันมุ่งเน้นไปที่การย่อยสลายarrayEqual
เป็นขั้นตอนเล็ก ๆ มันเป็นแบบฝึกหัดที่น่าสนใจ แต่ไม่ใช่วิธีที่ดีที่สุด (ในทางปฏิบัติมากที่สุด) ในการแก้ไขปัญหานี้ หากคุณสนใจคุณสามารถดูประวัติการแก้ไขนี้ได้