Как создать хеш в Ruby, который сравнивает строки, игнорируя регистр? - PullRequest
16 голосов
/ 08 января 2010

В Ruby я хочу хранить некоторые вещи в Hash, но я не хочу, чтобы они учитывали регистр. Так, например:

h = Hash.new
h["HELLO"] = 7
puts h["hello"]

Это должно вывести 7, даже если дело обстоит иначе. Могу ли я просто переопределить метод равенства хэша или что-то подобное?

Спасибо.

Ответы [ 6 ]

16 голосов
/ 08 января 2010

Если вы действительно хотите игнорировать регистр в обоих направлениях и обрабатывать все методы хеширования, такие как #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
15 голосов
/ 08 января 2010

Чтобы это изменение не нарушало полностью независимые части вашей программы (например, другие используемые вами рубиновые гемы), создайте отдельный класс для вашего нечувствительного хэша.

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), необходимых для полной реализации. Приведенный выше шаблон можно легко распространить на все эти связанные методы.

1 голос
/ 08 января 2010
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
1 голос
/ 08 января 2010

В общем, я бы сказал, что это плохой план; однако на вашем месте я бы создал подкласс хэша, который переопределяет метод []:

class SpecialHash < Hash
  def [](search)
    # Do special code here
  end
end
1 голос
/ 08 января 2010

Есть ли причина не просто использовать строку # в верхнем регистре?

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.

0 голосов
/ 10 июля 2012

Хотя подход Райана МакГири работает великолепно и почти наверняка является правильным способом сделать это, есть ошибка, которую я не смог определить, которая нарушает метод 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}
...