เมื่อคุณใช้บางสิ่งมากเท่ากับนักพัฒนา Ruby ที่ใช้ Hashes คุณจะคิดว่าคุณได้เห็นมันทั้งหมดแล้ว
แต่ฉันมาที่นี่เพื่อบอกคุณว่า Hash ทับทิมผู้ต่ำต้อยมีเคล็ดลับบางอย่าง ห่างไกลจากการเป็นระบบคีย์-ค่าที่โง่เขลา ออบเจ็กต์ Hash ช่วยให้คุณทำสิ่งที่น่าสนใจและซับซ้อนได้
วัตถุใดๆ ก็สามารถเป็นคีย์แฮชได้
ก่อนที่เราจะไปไกลกว่านี้ฉันอยากจะชี้ให้เห็นสิ่งหนึ่งที่อาจไม่ชัดเจน แม้ว่าเรามักจะใช้สตริงและสัญลักษณ์เป็นแฮชคีย์ แต่ก็ไม่ได้หมายความว่าเราจะใช้อ็อบเจกต์ประเภทอื่นไม่ได้เช่นกัน อันที่จริง คุณสามารถใช้ได้เกือบทุกอย่างเป็นแฮชคีย์
# Numbers can be hash keys
{1 => "one"}[1] # "one"
# So can the Ruby kernel
{Kernel => 1}[Kernel] # 1
# You can store values for specific classes
{Kernel => 1, String => 2}["hello world".class] # 2
# You can store values for booleans
{true => "verdad"}[1==1] # "verdad"
# You can even use complex arrays and even other hashes as hash keys
{[[1,0],[0,1]] => "identity matrix"}[[[1,0], [0,1]]] # "identity matrix"
ตัวเลือกเหล่านี้บางส่วนมีประโยชน์มากกว่าตัวเลือกอื่นๆ แต่พร้อมให้คุณใช้งานแล้ว
คุณสามารถควบคุมค่าเริ่มต้นได้
สมมติว่าคุณมีแฮช h={ a: 1 }
. หากคุณพยายามเข้าถึงค่าที่ไม่มีอยู่ - ตัวอย่างเช่น h[:x]
- คุณได้รับศูนย์ นั่นเป็นเพราะว่าไม่มีเป็นค่าเริ่มต้นของทุกแฮช เว้นแต่คุณจะระบุเป็นอย่างอื่น
คุณสามารถตั้งค่าเริ่มต้นสำหรับแฮชใหม่ได้โดยส่งอาร์กิวเมนต์ไปยังตัวสร้าง
h = Hash.new("This attribute intentionally left blank")
h[:a] = 1
h[:a] # 1
h[:x] # "This attribute intentionally left blank"
ค่าเริ่มต้นแบบไดนามิก
ให้ความสนใจ เพราะนี่คือเคล็ดลับที่เป็นพื้นฐานของทุกสิ่งที่ตามมา
หากคุณส่งบล็อกไปยังตัวสร้าง คุณจะสร้างค่าเริ่มต้นได้โดยทางโปรแกรม ในตัวอย่างด้านล่าง ฉันได้เพิ่มการประทับเวลาให้กับค่าเริ่มต้น ดังนั้นคุณจะเห็นได้ว่าค่านั้นถูกสร้างขึ้นแบบไดนามิก
h = Hash.new { |hash, key| "#{key}: #{ Time.now.to_i }" }
h[:a] # "a: 1435682937"
h[:a] # "a: 1435682941"
h[:b] # "b: 1435682943"
นี่เป็นสิ่งสำคัญเนื่องจากบล็อก "ค่าเริ่มต้น" สามารถทำสิ่งอื่นนอกเหนือจากการคืนค่าเริ่มต้น
การเพิ่มข้อยกเว้นหากไม่มีคีย์แฮช
ปัญหาหลักอย่างหนึ่งของแฮชคือแฮชไม่สำเร็จ คุณพิมพ์ user[:phnoe]
. โดยไม่ได้ตั้งใจ แทน user[:phone]
และแทนที่จะสร้างข้อยกเว้น แฮชจะคืนค่าเป็นศูนย์ แต่คุณสามารถเปลี่ยนพฤติกรรมนี้ได้
h = Hash.new { |hash, key| raise ArgumentError.new("No hash key: #{ key }") }
h[:a]=1
h[:a] # 1
h[:x] # raises ArgumentError: No hash key: x
เทคนิคนี้มีประโยชน์สำหรับการดีบักและการปรับโครงสร้างใหม่ เนื่องจากใช้กับแฮชเฉพาะ เป็นวิธีที่รบกวนน้อยกว่ามากในการเพิ่มพฤติกรรมนี้มากกว่าสิ่งที่ต้องการลิงแก้ไขคลาส Hash
หมายเหตุ:ฉันไม่ได้แนะนำให้ใครใช้สิ่งนี้แทน Hash.fetch ในโค้ดใหม่ เป็นเพียงเคล็ดลับที่น่าสนใจในการจัดเตรียมการดีบักและการจัดโครงสร้างใหม่
ตารางค้นหาที่สร้างขึ้นอย่างเกียจคร้าน
เทคนิคนี้มีประโยชน์สำหรับการแคชผลลัพธ์ของการคำนวณ ลองนึกภาพว่าคุณต้องคำนวณรากที่สองจำนวนมาก คุณสามารถสร้างตารางค้นหาแบบขี้เกียจได้ดังตัวอย่างด้านล่าง
sqrt_lookup = Hash.new { |hash, key| hash[key] = Math.sqrt(key) }
sqrt_lookup[9] # 3.0
sqrt_lookup[7] # 2.6457513110645907
sqrt_lookup # {9=>3.0, 7=>2.6457513110645907}
ตารางค้นหาขี้เกียจแบบเรียกซ้ำ
สมมติว่าคุณมีฟังก์ชันแบบเรียกซ้ำและต้องการแคชผลลัพธ์ของการเรียกซ้ำแต่ละครั้ง ยกตัวอย่างการคำนวณแบบแฟกทอเรียล "โฟร์แฟกทอเรียล" หรือ "4!" เป็นอีกวิธีหนึ่งในการพูดว่า "4x3x2x1" คุณสามารถใช้ซ้ำได้โดยใช้แฮช ตัวอย่างด้านล่างซึ่งฉันได้นำมาจากโพสต์ในบล็อกนี้แสดงให้เห็นอย่างชัดเจน:
factorial = Hash.new do |h,k|
if k > 1
h[k] = h[k-1] * k
else
h[k] = 1
end
end
factorial[4] # 24
factorial # {1=>1, 2=>2, 3=>6, 4=>24}
การแก้ไขค่าเริ่มต้นหลังจากการเริ่มต้น
คุณยังสามารถควบคุมค่าเริ่มต้นได้หลังจากสร้างแฮชแล้ว ในการดำเนินการนี้ให้ใช้ default
และ default_proc
เซ็ตเตอร์
h={}
h[:a] # nil
h.default = "new default"
h[:a] # "new default"
h.default_proc = Proc.new { Time.now.to_i }
h[:a] # 1435684014
Find the Ruby:เกมแฮชที่ซ้อนกันอย่างไม่สิ้นสุดอย่างเกียจคร้าน
เพื่อความสนุก เรามารวมเทคนิคที่มีประโยชน์เหล่านี้ไว้ในตัวอย่างที่ไร้ประโยชน์อย่างยิ่ง จำเกมผจญภัยแบบข้อความเก่าได้หรือไม่? มาสร้างเวอร์ชันที่โง่ที่สุดกันเถอะ
ลองนึกภาพคุณอยู่ในถ้ำ คุณสามารถไปเหนือ ใต้ ตะวันออก หรือตะวันตก สามตัวเลือกเหล่านี้จะพาคุณไปยัง "ห้อง" ใหม่ของถ้ำที่คุณสำรวจต่อไป แต่ทางเลือกหนึ่งจะนำคุณไปสู่ "ทับทิม" ดังนั้นชื่อของเกม:"ค้นหาทับทิม"
แต่ละห้องในถ้ำสอดคล้องกับแฮช แฮชมีรายการเดียวเท่านั้น หนึ่งใน ["n", "s", "e", "w"] ที่ถูกสุ่มเลือก มีค่าเป็น "You found the ruby" หากคุณเลือกไม่ถูกต้อง แฮชใหม่จะถูกสร้างขึ้นและเพิ่มลงในทรี
generator = Proc.new do |hash, key|
hash[key] = Hash.new(&generator).merge(["n", "s", "e", "w"][rand(4)] => "You found the ruby!")
end
dungeon = Hash.new(&generator)
dungeon["n"] # <Hash ...
dungeon["n"]["s"] # <Hash ...
dungeon["n"]["s"]["w"] # "You found the ruby!"