How do I create a hash in Ruby that compares strin

2020-02-05 02:24发布

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.

标签: ruby hash key
6条回答
你好瞎i
2楼-- · 2020-02-05 03:03

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}
查看更多
在下西门庆
3楼-- · 2020-02-05 03:04

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
查看更多
我命由我不由天
4楼-- · 2020-02-05 03:20

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.

查看更多
Root(大扎)
5楼-- · 2020-02-05 03:22

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.

查看更多
疯言疯语
6楼-- · 2020-02-05 03:22
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
查看更多
男人必须洒脱
7楼-- · 2020-02-05 03:26

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
查看更多
登录 后发表回答