Khi bạn sử dụng một thứ gì đó nhiều như các nhà phát triển Ruby sử dụng Hashes, bạn sẽ dễ dàng nghĩ rằng bạn đã thấy tất cả.
Nhưng tôi ở đây để nói với bạn rằng ruby Hash khiêm tốn có một vài thủ thuật trong tay áo của nó. Khác xa với một hệ thống khóa-giá trị ngu ngốc, đối tượng Hash cung cấp cho bạn sức mạnh để thực hiện một số điều rất thú vị và phức tạp.
Mọi đối tượng đều có thể là khóa băm
Trước khi chúng ta đi xa hơn, tôi muốn chỉ ra một điều có thể không rõ ràng. Mặc dù chúng ta có xu hướng sử dụng chuỗi và ký hiệu làm khóa băm, nhưng điều đó không có nghĩa là chúng ta cũng không thể sử dụng các loại đối tượng khác. Trên thực tế, bạn có thể sử dụng hầu hết mọi thứ làm khóa băm.
# 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"
Một số tùy chọn trong số này hữu ích hơn những tùy chọn khác, nhưng tất cả chúng đều có sẵn cho bạn.
Bạn có quyền kiểm soát giá trị mặc định.
Giả sử bạn có một băm h={ a: 1 }
. Nếu bạn cố gắng truy cập một giá trị không tồn tại - ví dụ:h[:x]
- bạn nhận được không. Đó là bởi vì nil là giá trị mặc định của mọi hàm băm, trừ khi bạn chỉ định khác.
Bạn có thể đặt giá trị mặc định cho một hàm băm mới bằng cách chuyển một đối số vào hàm tạo của nó.
h = Hash.new("This attribute intentionally left blank")
h[:a] = 1
h[:a] # 1
h[:x] # "This attribute intentionally left blank"
Giá trị mặc định động
Hãy chú ý, vì đây là thủ thuật là nền tảng của mọi thứ sau đó.
Nếu bạn truyền một khối vào hàm tạo, bạn có thể tạo các giá trị mặc định theo chương trình. Trong ví dụ bên dưới, tôi đã thêm dấu thời gian vào giá trị mặc định, vì vậy bạn có thể thấy rằng nó đang được tạo động.
h = Hash.new { |hash, key| "#{key}: #{ Time.now.to_i }" }
h[:a] # "a: 1435682937"
h[:a] # "a: 1435682941"
h[:b] # "b: 1435682943"
Điều này quan trọng vì khối "giá trị mặc định" có thể làm những việc khác ngoài việc trả về giá trị mặc định.
Tăng một ngoại lệ nếu không có khóa băm
Một trong những vấn đề chính của băm là chúng không hoạt động một cách âm thầm. Bạn vô tình nhập user[:phnoe]
thay vì user[:phone]
và thay vì nâng cao một ngoại lệ, hàm băm trả về nil. Nhưng bạn có thể thay đổi hành vi này.
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
Kỹ thuật này có thể hữu ích cho việc gỡ lỗi và tái cấu trúc vì nó áp dụng cho một hàm băm cụ thể. Đó là một cách ít xâm phạm hơn để thêm hành vi này hơn là một cách như khỉ vá lớp Hash.
Lưu ý:Tôi không đề nghị bất kỳ ai sử dụng điều này thay cho Hash.fetch trong mã mới. Đó chỉ là một thủ thuật thú vị để bạn chuẩn bị sẵn sàng để gỡ lỗi và tái cấu trúc.
Các bảng tra cứu do Lazily tạo
Kỹ thuật này hữu ích để lưu vào bộ nhớ đệm các kết quả của một phép tính. Hãy tưởng tượng rằng bạn cần tính rất nhiều căn bậc hai. Bạn có thể tạo một bảng tra cứu được điền chậm như ví dụ bên dưới.
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}
Bảng tra cứu đệ quy lười biếng
Giả sử bạn có một hàm đệ quy và bạn muốn lưu vào bộ nhớ cache kết quả của mỗi lần đệ quy. Hãy lấy một phép tính giai thừa làm ví dụ. "Bốn giai thừa", hay còn gọi là "4!" chỉ là một cách nói khác của "4x3x2x1." Bạn có thể thực hiện điều này một cách đệ quy bằng cách sử dụng một hàm băm. Ví dụ dưới đây, mà tôi đã lấy từ bài đăng trên blog này thể hiện điều đó một cách độc đáo:
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}
Sửa đổi giá trị mặc định sau khi khởi tạo
Bạn cũng có thể kiểm soát giá trị mặc định sau khi một hàm băm đã được tạo. Để thực hiện việc này, hãy sử dụng default
và default_proc
người định cư.
h={}
h[:a] # nil
h.default = "new default"
h[:a] # "new default"
h.default_proc = Proc.new { Time.now.to_i }
h[:a] # 1435684014
Tìm Ruby:Trò chơi của các hàm băm lồng nhau vô hạn một cách lười biếng
Chỉ cho vui thôi, hãy gộp tất cả những kỹ thuật hữu ích này vào một ví dụ cực kỳ vô dụng. Bạn còn nhớ trò chơi Adventure dựa trên văn bản cũ đó không? Hãy xây dựng phiên bản ngu ngốc nhất từ trước đến nay.
Hãy tưởng tượng bạn đang ở trong một hang động. Bạn có thể đi về phía bắc, nam, đông hoặc tây. Ba trong số những lựa chọn này sẽ đưa bạn đến một "căn phòng" mới của hang động, nơi bạn tiếp tục khám phá. Nhưng một sự lựa chọn dẫn bạn đến một "viên hồng ngọc". Do đó, tên của trò chơi:"tìm viên hồng ngọc".
Mỗi phòng trong hang tương ứng với một băm. Hàm băm chỉ có một mục nhập. Một trong ["n", "s", "e", "w"] được chọn ngẫu nhiên, có giá trị "Bạn đã tìm thấy viên ruby". Nếu bạn chọn không chính xác, một hàm băm mới sẽ được tạo và thêm vào cây.
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!"