【发布时间】:2011-01-03 01:45:21
【问题描述】:
在 Ruby 中,我想在 Hash 中存储一些东西,但我不希望它区分大小写。比如:
h = Hash.new
h["HELLO"] = 7
puts h["hello"]
这应该输出 7,即使大小写不同。我可以只覆盖哈希的相等方法或类似的方法吗?
谢谢。
【问题讨论】:
在 Ruby 中,我想在 Hash 中存储一些东西,但我不希望它区分大小写。比如:
h = Hash.new
h["HELLO"] = 7
puts h["hello"]
这应该输出 7,即使大小写不同。我可以只覆盖哈希的相等方法或类似的方法吗?
谢谢。
【问题讨论】:
有什么理由不只使用 string#upcase?
h = Hash.new
h["HELLO"] = 7
puts h["hello".upcase]
如果你坚持要修改hash,你可以这样做
class Hash
alias :oldIndexer :[]
def [](val)
if val.respond_to? :upcase then oldIndexer(val.upcase) else oldIndexer(val) end
end
end
既然提出来了,你也可以这样做让设置不区分大小写:
class Hash
alias :oldSetter :[]=
def []=(key, value)
if key.respond_to? :upcase then oldSetter(key.upcase, value) else oldSetter(key, value) end
end
end
我还建议使用 module_eval。
【讨论】:
总的来说,我会说这是一个糟糕的计划;但是,如果我是你,我会创建一个覆盖 [] 方法的哈希子类:
class SpecialHash < Hash
def [](search)
# Do special code here
end
end
【讨论】:
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
【讨论】:
如果你真的想在两个方向上忽略大小写并处理所有 Hash 方法,如 #has_key?、#fetch、#values_at、#delete 等,如果你愿意,你需要做一些工作从头开始构建它,但是如果您创建一个从类 ActiveSupport::HashWithIndifferentAccess 扩展的新类,您应该可以很容易地做到这一点:
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
以下是一些示例行为:
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
【讨论】:
为防止此更改完全破坏程序的独立部分(例如您正在使用的其他 ruby gem),请为您的不敏感哈希创建一个单独的类。
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
在覆盖分配和检索函数之后,它几乎是小菜一碟。创建 Hash 的完全替代品需要更加细致地处理完整实现所需的别名和其他功能(例如,#has_key? 和 #store)。上面的模式可以很容易地扩展到所有这些相关的方法。
【讨论】:
虽然 Ryan McGeary 的方法效果很好,而且几乎可以肯定是正确的方法,但有一个我无法找出原因的错误,它破坏了 Hash[] 方法。
例如:
r = CaseInsensitiveHash['ASDF', 1, 'QWER', 2]
=> {"ASDF"=>1, "QWER"=>2}
r['ASDF']
=> nil
ap r
{
"ASDF" => nil,
"QWER" => nil
}
=> {"ASDF"=>1, "QWER"=>2}
虽然我无法找到或修复错误的根本原因,但以下 hack 确实可以改善问题:
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}
【讨论】: