Как создать хэш в Ruby, который сравнивает строки, игнорируя регистр?
В Ruby я хочу хранить некоторые вещи в Hash, но я не хочу, чтобы это зависало от регистра. Так, например:
h = Hash.new
h["HELLO"] = 7
puts h["hello"]
Это должно выводить 7, хотя случай отличается. Могу ли я просто переопределить метод равенства хэша или что-то подобное?
Спасибо.
Ответы
Ответ 1
Чтобы это изменение не полностью разрушало независимые части вашей программы (например, другие рубиновые камни, которые вы используете), создайте отдельный класс для вашего нечувствительного хэша.
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), необходимых для полной реализации. Вышеприведенный шаблон можно легко распространить на все эти связанные методы.
Ответ 2
Если вы действительно хотите игнорировать регистр в обоих направлениях и обрабатывать все методы хэша, такие как #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
Ответ 3
Любая причина не просто использовать строку # upcase?
h = Hash.new
h["HELLO"] = 7
puts h["hello".upcase]
Если вы настаиваете на изменении хэша, вы можете сделать что-то вроде следующего
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.
Ответ 4
В общем, я бы сказал, что это плохой план; однако, если бы я был вами, я бы создал подкласс хэш, который переопределяет метод []
:
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
В то время как подход Райана МакГери работает отлично и почти наверняка является правильным способом сделать это, есть ошибка, из-за которой я не смог распознать причину, которая нарушает метод Hash[]
.
Например:
r = CaseInsensitiveHash['ASDF', 1, 'QWER', 2]
=> {"ASDF"=>1, "QWER"=>2}
r['ASDF']
=> nil
ap r
{
"ASDF" => nil,
"QWER" => nil
}
=> {"ASDF"=>1, "QWER"=>2}
Хотя мне не удалось найти или исправить основную причину ошибки, следующий хак облегчит проблему:
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}