Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

เชี่ยวชาญ Ruby Hashes: เทคนิคขั้นสูงและฟีเจอร์แอบแฝง

เวลาใช้ Hash บ่อยๆ อย่างนักพัฒนา Ruby น่าจะคิดว่าเคยเห็นหมดแล้ว

แต่ผมบอกเลยว่า Hash ที่ดูธรรมดานี้ยังมีทริคหลายอย่างให้คุณค้นพบ ไกล่จากระบบ key-value ธรรมดา Hash object ให้อำนาจทำสิ่งน่าสนใจและซับซ้อนได้

Object ใดๆ ก็เป็น Hash key ได้

ก่อนไปต่อ ผมอยากชี้ให้เห็นอย่างหนึ่งที่อาจไม่ชัดเจน แม้เรามักใช้ strings และ symbols เป็น hash keys แต่ไม่ได้หมายความว่าไม่สามารถใช้ object ประเภทอื่นได้ จริงๆ แล้วเกือบทุกอย่างเป็น key ได้

# Numbers can be hash keys {1 => "one"}[1] # "one" # Kernel ก็เป็น key ได้ {Kernel => 1}[Kernel] # 1 # เก็บค่าไว้สำหรับ class เฉพาะ {Kernel => 1, String => 2}["hello world".class] # 2 # เก็บค่าไว้สำหรับ booleans {true => "verdad"}[1==1] # "verdad" # ใช้ complex arrays และ hashes อื่นๆ เป็น key ได้ {[[1,0],[0,1]] => "identity matrix"}[[[1,0], [0,1]]] # "identity matrix" 

บางออปชันมีประโยชน์กว่าบ้าง แต่มันพร้อมให้ใช้งานทั้งหมด

คุณควบคุมค่า default ได้

สมมติมี hash h={ a: 1 } ถ้าพยายาม access key ที่ไม่มี เช่น h[:x] จะได้ nil เพราะ nil เป็น default ของทุก hash เว้นแต่จะระบุเป็นอย่างอื่น

ตั้งค่า default ให้ hash ใหม่ได้โดยส่ง argument เข้า constructor

h = Hash.new("This attribute intentionally left blank") h[:a] = 1 h[:a] # 1 h[:x] # "This attribute intentionally left blank" 

ค่า Default แบบ Dynamic

ให้ความสนใจตรงนี้ เพราะมันเป็นพื้นฐานของทุกอย่างต่อไป

ถ้าส่ง block เข้า constructor สร้างค่า default แบบโปรแกรมได้ ในตัวอย่างเพิ่ม timestamp เข้า default value เพื่อเห็นว่ามัน generate แบบ dynamic

h = Hash.new { |hash, key| "#{key}: #{ Time.now.to_i }" } h[:a] # "a: 1435682937" h[:a] # "a: 1435682941" h[:b] # "b: 1435682943" 

สำคัญเพราะ block "default value" ทำได้ไม่ใช่แค่ return ค่า default

Raise Exception ถ้า Hash Key ไม่มี

ปัญหาหลักของ hash คือ fail silently พิมพ์ user[:phnoe] แทน user[:phone] แทนที่จะ raise exception hash คืน nil แต่นี่เปลี่ยนได้

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 

เทคนิคนี้ช่วย debugging และ refactoring เพราะ apply กับ hash เฉพาะตัว น้อยกว่า monkey-patch Hash class

Note: ไม่ได้แนะนำใช้แทน Hash.fetch ในโค้ดใหม่ แค่ trick สนุกสำหรับ debug และ refactor

Lookup Tables แบบ Lazy-Generated

เทคนิคนี้สำหรับ cache ผลลัพธ์การคำนวณ สมมติต้องคำนวณ square roots เยอะ สร้าง lookup table ที่ populate เองแบบ lazy ได้

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} 

Recursive Lazy Lookup Tables

สมมติมี recursive function อยาก cache ผลแต่ละ recursion ใช้ factorial คำนวณตัวอย่าง "4!" คือ "4x3x2x1" implement แบบ recursive ด้วย hash ได้:

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} 

แก้ไข Defaults หลัง Initialize แล้ว

ควบคุม default value หลังสร้าง hash ได้ด้วย default และ default_proc setters

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: เกม Nested Hashes แบบ Lazy Infinite

รวมเทคนิคทั้งหมดเข้าไปในตัวอย่าง "ใช้ไม่ได้" สุดๆ เพื่อสนุก เกม Adventure เห็นไหม? สร้างเวอร์ชันโง่ที่สุด

คุณอยู่ในถ้ำ ไปได้ทิศเหนือ ใต้ ตะวันออก ตะวันตก 3 ทิศนำไปสู่ "room" ใหม่ แต่อีก 1 ทิศนำไปสู่ "ruby" ดังนั้นเกมชื่อ "find the ruby"

แต่ละ room = hash มี entry เดียว หนึ่งใน ["n", "s", "e", "w"] random มีค่า "You found the ruby." เลือกผิด สร้าง hash ใหม่เพิ่มเข้า tree

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!"