How do I create a hash in Ruby that compares strings, ignoring case?
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 endendyou_insensitive = HashClod.newyou_insensitive['clod'] = 1puts you_insensitive['cLoD'] # => 1you_insensitive['CLod'] = 5puts 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.
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.newh["HELLO"] = 7h.fetch("HELLO") # => 7h.fetch("hello") # => 7h["HELLO"] # => 7h["hello"] # => 7h.has_key?("hello") # => trueh.values_at("hello", "HELLO") # => [7, 7]h.delete("hello") # => 7h["HELLO"] # => nil
Any reason for not just using string#upcase?
h = Hash.newh["HELLO"] = 7puts h["hello".upcase]
If you insist on modifying hash, you can do something like the following
class Hashalias :oldIndexer :[]def [](val) if val.respond_to? :upcase then oldIndexer(val.upcase) else oldIndexer(val) endendend
Since it was brought up, you can also do this to make setting case insensitive:
class Hashalias :oldSetter :[]=def []=(key, value) if key.respond_to? :upcase then oldSetter(key.upcase, value) else oldSetter(key, value) endendend
I also recommend doing this using module_eval.