How do I create a hash in Ruby that compares strings, ignoring case?

前端 未结 6 1336
無奈伤痛
無奈伤痛 2020-12-09 16:34

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\"]


        
相关标签:
6条回答
  • 2020-12-09 17:12

    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}
    
    0 讨论(0)
  • 2020-12-09 17:19

    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
    
    0 讨论(0)
  • 2020-12-09 17:25

    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.

    0 讨论(0)
  • 2020-12-09 17:30
    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
    
    0 讨论(0)
  • 2020-12-09 17:31

    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.

    0 讨论(0)
  • 2020-12-09 17:33

    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
    
    0 讨论(0)
提交回复
热议问题