tags:

views:

278

answers:

5

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  A: 

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.

mletterle
h["hElLo"] = 7?
OedipusPrime
Assumption was made that setting the hash was being done with all caps. You can easily extend the above to []=
mletterle
+1  A: 

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
Topher Fangio
Just to add a bit more to this answer: Override #[]= to call #downcase on the keys you receive and then in #[] you can call self.get(search.downcase).
Federico Builes
@Federico Builes - +1 - Thanks for the extra bit :-)
Topher Fangio
+1  A: 
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
Jörg W Mittag
This will, of course, blow up if key does not implement a method named upcase...
mletterle
+4  A: 

If you really want to ignore case in both directions, 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'

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

(Use ActiveSupport::HashWithIndifferentAccess in activesupport v3+ and HashWithIndifferentAccess in activesupport v2.3.x)

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
Ryan McGeary
Neat, I didn't know about that!
Topher Fangio
+3  A: 

To prevent this change from completely breaking independant 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)
    key.respond_to?(:upcase) ? super(key.upcase) : super(key)
  end

  def []=(key, value)
    key.respond_to?(:upcase) ? super(key.upcase, value) : super(key, value)
  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.

Myrddin Emrys