【问题标题】:What's the best way i can iterate over a hash in this manner?我可以以这种方式迭代哈希的最佳方式是什么?
【发布时间】:2021-03-05 17:09:47
【问题描述】:

所以我有这个哈希,我想写一个程序,给定一个人的分数可以告诉他们:

a)无论他们是否对特定物品过敏

b)完整的过敏列表。

allergies = {
        1 => "eggs",               
        2 => "peanuts",            
        4 => "shellfish",         
        8 => "strawberries",   
        16 => "tomatoes", 
        32 => "chocolate", 
        64 => "pollen", 
        128 => "cat",
    }

我想过我将如何实现这一目标。请参阅下面的 cmets。

 # 1+2 = eggs & peanuts (3)        # 1+2+4 = eggs & peanuts & shellfish (7)         # 1+2+4+8 = eggs & peanuts & shellfish & strawberries (15)
 # 1+4 = eggs & shellfish (5)      # 1+2+8 = eggs & peanuts & strawberries (11)     # 1+2+4+16 = eggs & peanuts & shellfish & tomatoes (23)
 # 1+8 = eggs & strawberries (9)   # 1+2+16 = eggs & peanuts & tomatoes (19)        # 1+2+4+32 = eggs & peanuts & shellfish & chocolate (39)
 # 1+16 = eggs & tomatoes (17)     # 1+2+32 = eggs & peanuts & chocolate (35)       # 1+2+4+64 = eggs & peanuts & shellfish & pollen (71)
 # 1+32 = eggs & chocolate (33)    # 1+2+64 = eggs & peanuts & pollen (67)          # 1+2+4+128 = eggs & peanuts & shellfish & cat (135)
 # 1+64 = eggs & pollen (65)       # 1+2+128 = eggs & peanuts & car (131)           #etc
 # 1+128 = eggs & cat (129)                                                         

所以我可以使用的帮助是,我如何遍历哈希,确保考虑到所有 255 个可能的组合并返回与该可能组合关联的键,这样当我调用带有 35 参数的方法,它会返回值 ["eggs", "peanuts", "chocolate"]。 希望这是有道理的。

我愿意接受您可能需要实现的其他想法。

谢谢!

【问题讨论】:

    标签: ruby loops hash combinations key-value


    【解决方案1】:
    allergies = {
       1 => "eggs",               
       2 => "peanuts",            
       4 => "shellfish",        
       8 => "strawberries",
      16 => "tomatoes"  
    }
    
    def nbr_to_allergies(allergies, n)
      allergies.values_at(*allergies.keys.select { |k| n & k == k })
    end
    
    puts "nbr    allergies"
    puts "-" * 64
    (0..2**(allergies.size)-1).each do |n|
      puts "#{n.to_s.rjust(2)}: #{nbr_to_allergies(allergies, n)}"
    end
    

    显示以下内容。

    nbr    allergies
    ----------------------------------------------------------------
     0: []
     1: ["eggs"]
     2: ["peanuts"]
     3: ["eggs", "peanuts"]
     4: ["shellfish"]
     5: ["eggs", "shellfish"]
     6: ["peanuts", "shellfish"]
     7: ["eggs", "peanuts", "shellfish"]
     8: ["strawberries"]
     9: ["eggs", "strawberries"]
    10: ["peanuts", "strawberries"]
    11: ["eggs", "peanuts", "strawberries"]
    12: ["shellfish", "strawberries"]
    13: ["eggs", "shellfish", "strawberries"]
    14: ["peanuts", "shellfish", "strawberries"]
    15: ["eggs", "peanuts", "shellfish", "strawberries"]
    16: ["tomatoes"]
    17: ["eggs", "tomatoes"]
    18: ["peanuts", "tomatoes"]
    19: ["eggs", "peanuts", "tomatoes"]
    20: ["shellfish", "tomatoes"]
    21: ["eggs", "shellfish", "tomatoes"]
    22: ["peanuts", "shellfish", "tomatoes"]
    23: ["eggs", "peanuts", "shellfish", "tomatoes"]
    24: ["strawberries", "tomatoes"]
    25: ["eggs", "strawberries", "tomatoes"]
    26: ["peanuts", "strawberries", "tomatoes"]
    27: ["eggs", "peanuts", "strawberries", "tomatoes"]
    28: ["shellfish", "strawberries", "tomatoes"]
    29: ["eggs", "shellfish", "strawberries", "tomatoes"]
    30: ["peanuts", "shellfish", "strawberries", "tomatoes"]
    31: ["eggs", "peanuts", "shellfish", "strawberries", "tomatoes"]
    

    请参阅 Hash#values_atInteger#&

    【讨论】:

    • 哇!谢谢你帮我解决这个问题。这对我来说是一个棘手的问题,现在我有了一个可以玩耍和发现新方法的发射台! :)
    【解决方案2】:
    combinations = 1.upto(allergies.keys.length).map do |i|
      allergies.values.combination(i).to_a
    end.flatten(1)
    # => 255 combinations
    
    lookup_hash = combinations.each_with_object({}) do |combo, memo|
      sum = combo.map do |allergy|
        allergies.key(allergy)
      end.sum
      memo[sum] = combo
    end
    
    lookup_hash[35]
    # => ["eggs", "peanuts", "chocolate"]
    

    首先,我们使用方便的Array#combination 方法获取所有组合。

    接下来,我们将数组中的数组转换为哈希,将总和(例如 35)映射到特定的成分组合。我们利用Hash#key 在成分散列(数字)中找到与特定值(字符串)对应的键。

    现在我们有了哈希值,我们可以查找任何数字。

    【讨论】:

    • 考虑使用flat_mapallergies.length 而不是allergies.keys.length
    • 哇!谢谢你帮我解决这个问题。这对我来说是一个棘手的问题,现在我有了一个可以玩耍和发现新方法的发射台! :)
    【解决方案3】:

    假设您已将您的过敏模式存储在一个整数变量 ap 中,即对于您的示例

    ap = 35
    

    您可以使用此模式获得过敏

    allergie_description = ap
                           .to_s(2) # convert into bit string
                           .split(//) # turn into array
                           .reverse # process right-to-left
                           .map # get position of "1" bits:
                           .with_index {|bit, pos| bit == '1' ? pos : nil}
                           .compact # remove "0" bits
                           .map {|pos| allergies[1<<pos]} # fetch allergy
                           .join(', ') # format nicely
    

    这使用了这样一个事实,即您的哈希键基本上表示位位置(.to_s(2) 创建一个位串),从 1 开始计数。

    您也可以按照您在问题中的要求使用这种方法预先计算所有可能的组合(通过循环遍历所需范围内的所有可能整数),但如果添加新的过敏症,组合的数量可能会变得如此之大,以至于最好动态计算组合值。

    【讨论】:

      【解决方案4】:

      试试这个方法

      allergies = {
        1 => "eggs",               
        2 => "peanuts",            
        4 => "shellfish",         
        8 => "strawberries",   
        16 => "tomatoes", 
        32 => "chocolate", 
        64 => "pollen", 
        128 => "cat",
      }
      
      def allergies_by_sum_of_keys(allergies, sum)
        allergies_keys = allergies.keys
        lookup = {} 
       
        (1..allergies_keys.length).each do |i|
          allergies_keys.combination(i).to_a.each do |keys|
            lookup[keys.sum] = keys.collect{|key| allergies[key] }
          end
        end
        
        lookup[sum] 
      end
      
      allergies_by_sum_of_keys(35)
      
      ["eggs", "peanuts", "chocolate"]
      

      【讨论】:

        【解决方案5】:

        由于您在此处有效地处理位集,因此您可以使用按位逻辑。具体来说,您可以使用带有合适掩码的按位AND 运算符来检查您的输入中是否设置了某个位。

        在您的情况下,您可以直接使用 allergy 键作为位掩码。您在那里选择的数字的共同点是它们可以存储在单个字节中,并且当以二进制表示时,它们中的每一个都在唯一的位置设置了一个位。

        这允许您使用按位 AND 运算符(在 Ruby 中为 the &amp; operator on Integers)检查输入中是否设置了此位:

        35 & 32
        # => 32
        
        35 & 16
        # => 0
        

        这可以通过使用Enumerable#filter_map 的非常简单的方法来使用,自 Ruby 2.7 起可用:

        ALLERGIES = {
          1 => "eggs",               
          2 => "peanuts",            
          4 => "shellfish",         
          8 => "strawberries",   
          16 => "tomatoes", 
          32 => "chocolate", 
          64 => "pollen", 
          128 => "cat",
        }
        
        def get_allergies(allergy_number)
          ALLERGIES.filter_map { |key, value| value if allergy_number & key > 0 }
        end
        
        get_allergies(35)
        # => ["eggs", "peanuts", "chocolate"]
        

        您可以使用类似的逻辑来检查给定的allergy_number 是否表示特定的命名过敏:

        def allergic?(allergy_number, allergy)
          key = ALLERGIES.key(allergy)
        
          # return false for unknown allergies
          return false unless key
        
          allergy_number & key > 0
        end
        
        allergic(35, "chocolate")
        # => true
        
        allergic(35, "shellfish")
        # => false
        

        同样,您可以使用按位 OR 操作根据给定的过敏列表(在 Ruby 中以 the | operator on Integers 提供)生成 allergy_number

        def allergy_number(allergies)
          allergies.inject(0) do |result, allergy|
            result | (ALLERGIES.key(allergy) || 0)
          end
        end
        
        allergy_number ["eggs", "peanuts", "chocolate"]
        # => 35
        

        【讨论】:

        • 很好的说明filter_map的使用。
        猜你喜欢
        • 2013-03-22
        • 1970-01-01
        • 2021-07-19
        • 1970-01-01
        • 2011-11-04
        • 2017-07-31
        • 1970-01-01
        • 2017-04-05
        • 2021-11-11
        相关资源
        最近更新 更多