可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
In Ruby, I want to store some stuff in a Hash, but I don't want it to be case-sensitive. So for example:
h = Hash.new
h["HELLO"] = 7
puts h["hello"]
This should output 7, even though the case is different. Can I just override the equality method of the hash or something similar?
Thanks.
回答1:
To prevent this change from completely breaking independent parts of your program (such as other ruby gems you are using), make a separate class for your insensitive hash.
class HashClod < Hash
def [](key)
super _insensitive(key)
end
def []=(key, value)
super _insensitive(key), value
end
# Keeping it DRY.
protected
def _insensitive(key)
key.respond_to?(:upcase) ? key.upcase : key
end
end
you_insensitive = HashClod.new
you_insensitive['clod'] = 1
puts you_insensitive['cLoD'] # => 1
you_insensitive['CLod'] = 5
puts you_insensitive['clod'] # => 5
After overriding the assignment and retrieval functions, it's pretty much cake. Creating a full replacement for Hash would require being more meticulous about handling the aliases and other functions (for example, #has_key? and #store) needed for a complete implementation. The pattern above can easily be extended to all these related methods.
回答2:
If you really want to ignore case in both directions and handle all Hash methods like #has_key?
, #fetch
, #values_at
, #delete
, etc. , you'll need to do a little work if you want to build this from scratch, but if you create a new class that extends from class ActiveSupport::HashWithIndifferentAccess, you should be able to do it pretty easily like so:
require "active_support/hash_with_indifferent_access"
class CaseInsensitiveHash < HashWithIndifferentAccess
# This method shouldn't need an override, but my tests say otherwise.
def [](key)
super convert_key(key)
end
protected
def convert_key(key)
key.respond_to?(:downcase) ? key.downcase : key
end
end
Here's some example behavior:
h = CaseInsensitiveHash.new
h["HELLO"] = 7
h.fetch("HELLO") # => 7
h.fetch("hello") # => 7
h["HELLO"] # => 7
h["hello"] # => 7
h.has_key?("hello") # => true
h.values_at("hello", "HELLO") # => [7, 7]
h.delete("hello") # => 7
h["HELLO"] # => nil
回答3:
Any reason for not just using string#upcase?
h = Hash.new
h["HELLO"] = 7
puts h["hello".upcase]
If you insist on modifying hash, you can do something like the following
class Hash
alias :oldIndexer :[]
def [](val)
if val.respond_to? :upcase then oldIndexer(val.upcase) else oldIndexer(val) end
end
end
Since it was brought up, you can also do this to make setting case insensitive:
class Hash
alias :oldSetter :[]=
def []=(key, value)
if key.respond_to? :upcase then oldSetter(key.upcase, value) else oldSetter(key, value) end
end
end
I also recommend doing this using module_eval.
回答4:
In general, I would say that this is a bad plan; however, if I were you, I'd create a subclass of hash that overrides the []
method:
class SpecialHash < Hash
def [](search)
# Do special code here
end
end
回答5:
require 'test/unit'
class TestCaseIndifferentHash < Test::Unit::TestCase
def test_that_the_hash_matches_keys_case_indifferent
def (hsh = {}).[](key) super(key.upcase) end
hsh['HELLO'] = 7
assert_equal 7, hsh['hello']
end
end
回答6:
While Ryan McGeary's approach works great and is almost certainly the correct way to do it, there is a bug that I haven't been able to discern the cause of, which breaks the Hash[]
method.
For example:
r = CaseInsensitiveHash['ASDF', 1, 'QWER', 2]
=> {"ASDF"=>1, "QWER"=>2}
r['ASDF']
=> nil
ap r
{
"ASDF" => nil,
"QWER" => nil
}
=> {"ASDF"=>1, "QWER"=>2}
Although I've not been able to find or fix the underlying cause of the bug, the following hack does ameliorate the problem:
r = CaseInsensitiveHash.new(Hash['ASDF', 1, 'QWER', 2])
=> {"asdf"=>1, "qwer"=>2}
r['ASDF']
=> 1
ap r
{
"asdf" => 1,
"qwer" => 2
}
=> {"asdf"=>1, "qwer"=>2}