ขั้นแรกโปรดทราบว่าลักษณะการทำงานนี้ใช้กับค่าเริ่มต้นที่มีการกลายพันธุ์ในภายหลัง (เช่นแฮชและสตริง) ไม่ใช่เฉพาะอาร์เรย์
TL; DR : ใช้Hash.new { |h, k| h[k] = [] }
ถ้าคุณต้องการวิธีแก้ปัญหาที่เป็นสำนวนมากที่สุดและไม่สนใจว่าทำไม
อะไรไม่ได้ผล
ทำไมHash.new([])
ไม่ทำงาน
มาดูรายละเอียดเพิ่มเติมเกี่ยวกับสาเหตุที่Hash.new([])
ไม่ได้ผล:
h = Hash.new([])
h[0] << 'a' #=> ["a"]
h[1] << 'b' #=> ["a", "b"]
h[1] #=> ["a", "b"]
h[0].object_id == h[1].object_id #=> true
h #=> {}
เราจะเห็นว่าออบเจ็กต์เริ่มต้นของเราถูกนำมาใช้ซ้ำและกลายพันธุ์ (เนื่องจากถูกส่งผ่านเป็นค่าเริ่มต้นเพียงค่าเดียวแฮชจึงไม่มีทางได้รับค่าเริ่มต้นใหม่ที่สดใหม่) แต่ทำไมจึงไม่มีคีย์หรือค่า ในอาร์เรย์แม้จะh[1]
ยังคงให้เราคุ้มค่า? นี่คือคำแนะนำ:
h[42] #=> ["a", "b"]
อาร์เรย์ที่ส่งคืนโดยการ[]
เรียกแต่ละครั้งเป็นเพียงค่าเริ่มต้นซึ่งเราได้ทำการเปลี่ยนแปลงตลอดเวลาดังนั้นตอนนี้จึงมีค่าใหม่ของเรา เนื่องจาก<<
ไม่ได้กำหนดให้กับแฮช (ไม่สามารถมอบหมายงานใน Ruby ได้หากไม่มี=
ของขวัญ† ) เราจึงไม่เคยใส่อะไรลงไปในแฮชจริงของเรา แต่เราต้องใช้<<=
(ซึ่งจะเป็นไป<<
ตาม+=
นั้น+
):
h[2] <<= 'c' #=> ["a", "b", "c"]
h #=> {2=>["a", "b", "c"]}
สิ่งนี้เหมือนกับ:
h[2] = (h[2] << 'c')
ทำไมHash.new { [] }
ไม่ทำงาน
การใช้Hash.new { [] }
ช่วยแก้ปัญหาในการนำกลับมาใช้ใหม่และการเปลี่ยนค่าเริ่มต้นเดิม (เนื่องจากบล็อกที่กำหนดจะถูกเรียกในแต่ละครั้งโดยส่งคืนอาร์เรย์ใหม่) แต่ไม่ใช่ปัญหาในการกำหนด:
h = Hash.new { [] }
h[0] << 'a' #=> ["a"]
h[1] <<= 'b' #=> ["b"]
h #=> {1=>["b"]}
ทำงานอะไร
วิธีการมอบหมาย
ถ้าเราจำที่จะใช้เสมอ<<=
แล้วHash.new { [] }
เป็นโซลูชั่นที่ทำงานได้ แต่มันเป็นบิตแปลกและไม่ใช่สำนวน (ผมไม่เคยเห็น<<=
ใช้ในป่า) นอกจากนี้ยังมีแนวโน้มที่จะเกิดข้อบกพร่องเล็กน้อยหาก<<
ใช้โดยไม่ได้ตั้งใจ
วิธีที่ไม่แน่นอน
เอกสารสำหรับHash.new
รัฐ (เน้นของตัวเอง):
หากระบุบล็อกบล็อกจะถูกเรียกด้วยวัตถุแฮชและคีย์และควรส่งคืนค่าเริ่มต้น มันเป็นความรับผิดชอบของบล็อกในการจัดเก็บค่าในกัญชาได้ตามต้องการ
ดังนั้นเราต้องเก็บค่าเริ่มต้นในแฮชจากภายในบล็อกหากเราต้องการใช้<<
แทน<<=
:
h = Hash.new { |h, k| h[k] = [] }
h[0] << 'a' #=> ["a"]
h[1] << 'b' #=> ["b"]
h #=> {0=>["a"], 1=>["b"]}
ได้อย่างมีประสิทธิภาพย้ายที่ได้รับมอบหมายจากสายของเราแต่ละคน (ซึ่งจะใช้<<=
) เพื่อกระชากผ่านไปเอาภาระของพฤติกรรมที่ไม่คาดคิดเมื่อใช้Hash.new
<<
โปรดทราบว่ามีความแตกต่างของการทำงานอย่างหนึ่งระหว่างวิธีนี้กับวิธีอื่น: วิธีนี้จะกำหนดค่าเริ่มต้นเมื่ออ่าน (เนื่องจากการกำหนดจะเกิดขึ้นภายในบล็อกเสมอ) ตัวอย่างเช่น:
h1 = Hash.new { |h, k| h[k] = [] }
h1[:x]
h1 #=> {:x=>[]}
h2 = Hash.new { [] }
h2[:x]
h2 #=> {}
วิธีที่ไม่เปลี่ยนรูป
คุณอาจสงสัยว่าทำไมHash.new([])
ไม่ทำงานในขณะที่Hash.new(0)
ทำงานได้ดี กุญแจสำคัญคือ Numerics ใน Ruby นั้นไม่เปลี่ยนรูปดังนั้นเราจึงไม่ต้องกลายพันธุ์ตามธรรมชาติ หากเราถือว่าค่าเริ่มต้นของเราไม่เปลี่ยนรูปเราก็สามารถใช้ได้Hash.new([])
เช่นกัน:
h = Hash.new([].freeze)
h[0] += ['a'] #=> ["a"]
h[1] += ['b'] #=> ["b"]
h[2] #=> []
h #=> {0=>["a"], 1=>["b"]}
([].freeze + [].freeze).frozen? == false
อย่างไรก็ตามโปรดทราบว่า ดังนั้นหากคุณต้องการให้แน่ใจว่าความไม่เปลี่ยนรูปจะถูกรักษาไว้ตลอดเวลาคุณต้องระมัดระวังในการตรึงวัตถุใหม่อีกครั้ง
สรุป
จากทุกวิธีโดยส่วนตัวแล้วฉันชอบ "วิธีที่ไม่เปลี่ยนรูป" - โดยทั่วไปแล้วความไม่เปลี่ยนแปลงทำให้การหาเหตุผลเกี่ยวกับสิ่งต่างๆนั้นง่ายกว่ามาก ท้ายที่สุดแล้วเป็นวิธีการเดียวที่ไม่มีความเป็นไปได้ของพฤติกรรมที่ไม่คาดคิดที่ซ่อนอยู่หรือละเอียดอ่อน อย่างไรก็ตามวิธีที่ใช้กันทั่วไปและเป็นสำนวนคือ“ วิธีที่ไม่แน่นอน”
ในฐานะที่เป็นครั้งสุดท้ายกันพฤติกรรมของค่าเริ่มต้นกัญชานี้ถูกบันทึกไว้ในทับทิม koans
†นี่ไม่เป็นความจริงอย่างเคร่งครัดวิธีการเช่นinstance_variable_set
ข้ามสิ่งนี้ แต่ต้องมีอยู่สำหรับการเขียนโปรแกรมเมตาเนื่องจากค่า l ใน=
ไม่สามารถเป็นไดนามิกได้