【问题标题】:What is the best way to convert an array to a hash in Ruby在Ruby中将数组转换为哈希的最佳方法是什么
【发布时间】:2010-09-07 13:30:00
【问题描述】:

在 Ruby 中,给定以下形式之一的数组...

[apple, 1, banana, 2]
[[apple, 1], [banana, 2]]

...将其转换为...形式的哈希的最佳方法是什么

{apple => 1, banana => 2}

【问题讨论】:

    标签: ruby arrays hashmap


    【解决方案1】:

    只需使用Hash[*array_variable.flatten]

    例如:

    a1 = ['apple', 1, 'banana', 2]
    h1 = Hash[*a1.flatten(1)]
    puts "h1: #{h1.inspect}"
    
    a2 = [['apple', 1], ['banana', 2]]
    h2 = Hash[*a2.flatten(1)]
    puts "h2: #{h2.inspect}"
    

    使用Array#flatten(1) 限制递归,因此Array 键和值按预期工作。

    【讨论】:

    • 哦,口才!这就是我喜欢 Ruby 的原因
    • 警告:如果你想要数组键或值,使用 flatten 的答案会导致问题。
    • 我在下面发布了一个替代解决方案,可以避免数组键或值出现问题。
    • 最好不要尝试为此做一个包罗万象的解决方案。如果您的键和值在 [[key1,value1],[key2,value2]] 中配对,则只需将其传递给 Hash[] 而不会增肥。哈希[a2] == 哈希[*a2.flatten]。如果数组已经像 [key1, value1, key2, value2] 那样被展平,那么只需在 var 前面加上 *, Hash[*a1]
    • FWIW,如果你真的想要(更多)一刀切的版本,你也可以使用Hash[*ary.flatten(1)],它将保留数组键和值。是递归的flatten 破坏了这些,这很容易避免。
    【解决方案2】:

    注意:如需简洁高效的解决方案,请参阅下方的Marc-André Lafortune's answer

    这个答案最初是作为使用 flatten 的方法的替代方法提供的,在撰写本文时,这种方法是最受好评的。我应该澄清一下,我并不打算将此示例作为最佳实践或有效方法。原始答案如下。


    警告!使用flatten的解决方案不会保留数组键或值!

    基于@John Topley 的流行答案,让我们尝试一下:

    a3 = [ ['apple', 1], ['banana', 2], [['orange','seedless'], 3] ]
    h3 = Hash[*a3.flatten]
    

    这会引发错误:

    ArgumentError: odd number of arguments for Hash
            from (irb):10:in `[]'
            from (irb):10
    

    构造函数期望一个偶数长度的数组(例如 ['k1','v1,'k2','v2'])。更糟糕的是,一个扁平化为偶数长度的不同 Array 只会默默地给我们一个值不正确的 Hash。

    如果要使用数组键或值,可以使用ma​​p

    h3 = Hash[a3.map {|key, value| [key, value]}]
    puts "h3: #{h3.inspect}"
    

    这会保留数组键:

    h3: {["orange", "seedless"]=>3, "apple"=>1, "banana"=>2}
    

    【讨论】:

    • 这与 Hash[a3] 相同,因为 a3 == a3.map{|k,v| [k,v]} 为真,它实际上等价于 a3.dup。
    • 不使用map,为什么不直接指定flatten的深度呢?例如:h3 = Hash[*a3.flatten(1)] 而不是 h3 = Hash[*a3.flatten],这会引发错误。
    • 这个答案效率不高。它也已过时。看我的回答。
    • 是的,我认为 Marc-André 的 to_h 更好。
    • @Marc-André Lafortune 谢谢你,我已经更新了我的答案,将用户引导到你的答案。
    【解决方案3】:

    最好的方法是使用Array#to_h

    [ [:apple,1],[:banana,2] ].to_h  #=> {apple: 1, banana: 2}
    

    注意to_h 也接受一个块:

    [:apple, :banana].to_h { |fruit| [fruit, "I like #{fruit}s"] } 
      # => {apple: "I like apples", banana: "I like bananas"}
    

    注意to_h 接受 Ruby 2.6.0+ 中的块;对于早期的红宝石,您可以使用我的 backports gem 和 require 'backports/2.6.0/enumerable/to_h'

    to_h 没有块是在 Ruby 2.1.0 中引入的。

    在 Ruby 2.1 之前,可以使用不太清晰的Hash[]

    array = [ [:apple,1],[:banana,2] ]
    Hash[ array ]  #= > {:apple => 1, :banana => 2}
    

    最后,警惕使用 flatten 的任何解决方案,这可能会导致数组本身的值出现问题。

    【讨论】:

    • 感谢新的 .to_h 方法的简单性!
    • 我比上面的答案更喜欢to_h 方法,因为它表达了在数组上操作之后转换的意图。
    • @BSeven Array#to_hEnumerable#to_h 均不在核心 ruby​​ 1.9 中。
    • 如果我有一个[[apple, 1], [banana, 2], [apple, 3], [banana, 4]] 的数组并且我希望输出为{"apple" =>[1,3], "banana"=>[2,4]} 怎么办?
    • @NishantKumar 这是一个不同的问题。
    【解决方案4】:

    更新

    Ruby 2.1.0 is released today。我附带了Array#to_hrelease notesruby-doc),它解决了将Array 转换为Hash 的问题。

    Ruby 文档示例:

    [[:foo, :bar], [1, 2]].to_h    # => {:foo => :bar, 1 => 2}
    

    【讨论】:

      【解决方案5】:

      编辑:看到我在写作时发布的回复,Hash[a.flatten] 似乎是要走的路。 当我考虑回复时,一定错过了文档中的那一点。认为如果需要,我编写的解决方案可以用作替代方案。

      第二种形式更简单:

      a = [[:apple, 1], [:banana, 2]]
      h = a.inject({}) { |r, i| r[i.first] = i.last; r }
      

      a = 数组,h = 哈希,r = 返回值哈希(我们累积的那个),i = 数组中的项目

      我能想到的最简洁的第一种形式是这样的:

      a = [:apple, 1, :banana, 2]
      h = {}
      a.each_slice(2) { |i| h[i.first] = i.last }
      

      【讨论】:

      • +1 表示a.inject({}) 单行,允许更灵活的值分配。
      • 也可以通过使用注入从第二个示例中删除h = {},以a.each_slice(2).inject({}) { |h,i| h[i.first] = i.last; h }结束
      • 你可以做a.each_slice(2).to_h
      【解决方案6】:

      您还可以使用以下方法简单地将二维数组转换为哈希:

      1.9.3p362 :005 > a= [[1,2],[3,4]]
      
       => [[1, 2], [3, 4]]
      
      1.9.3p362 :006 > h = Hash[a]
      
       => {1=>2, 3=>4} 
      

      【讨论】:

        【解决方案7】:

        总结 & TL;DR:

        此答案希望是对其他答案信息的全面总结。

        非常简短的版本,考虑到问题中的数据以及一些额外内容:

        flat_array   = [  apple, 1,   banana, 2  ] # count=4
        nested_array = [ [apple, 1], [banana, 2] ] # count=2 of count=2 k,v arrays
        incomplete_f = [  apple, 1,   banana     ] # count=3 - missing last value
        incomplete_n = [ [apple, 1], [banana   ] ] # count=2 of either k or k,v arrays
        
        
        # there's one option for flat_array:
        h1  = Hash[*flat_array]                     # => {apple=>1, banana=>2}
        
        # two options for nested_array:
        h2a = nested_array.to_h # since ruby 2.1.0    => {apple=>1, banana=>2}
        h2b = Hash[nested_array]                    # => {apple=>1, banana=>2}
        
        # ok if *only* the last value is missing:
        h3  = Hash[incomplete_f.each_slice(2).to_a] # => {apple=>1, banana=>nil}
        # always ok for k without v in nested array:
        h4  = Hash[incomplete_n] # or .to_h           => {apple=>1, banana=>nil}
        
        # as one might expect:
        h1 == h2a # => true
        h1 == h2b # => true
        h1 == h3  # => false
        h3 == h4  # => true
        

        讨论和细节如下。


        设置:变量

        为了显示我们将预先使用的数据,我将创建一些变量来表示数据的各种可能性。它们属于以下类别:

        根据问题中的直接内容,如a1a2

        (注意:我假设 applebanana 是用来表示变量的。正如其他人所做的那样,我将从这里开始使用字符串,以便输入和结果可以匹配。)我>

        a1 = [  'apple', 1 ,  'banana', 2  ] # flat input
        a2 = [ ['apple', 1], ['banana', 2] ] # key/value paired input
        

        多值键和/或值,如a3

        在其他一些答案中,提出了另一种可能性(我在这里展开)——键和/或值本身可能是数组:

        a3 = [ [ 'apple',                   1   ],
               [ 'banana',                  2   ],
               [ ['orange','seedless'],     3   ],
               [ 'pear',                 [4, 5] ],
             ]
        

        不平衡数组,如a4:

        为了更好的衡量标准,我想我会为我们可能输入不完整的情况添加一个:

        a4 = [ [ 'apple',                   1],
               [ 'banana',                  2],
               [ ['orange','seedless'],     3],
               [ 'durian'                    ], # a spiky fruit pricks us: no value!
             ]
        

        现在开始工作:

        从最初的扁平数组开始,a1

        有些人建议使用#to_h(在 Ruby 2.1.0 中出现,在早期版本中可以是backported)。对于最初的平面数组,这不起作用:

        a1.to_h   # => TypeError: wrong element type String at 0 (expected array)
        

        Hash::[]splat operator 结合使用:

        Hash[*a1] # => {"apple"=>1, "banana"=>2}
        

        这就是a1所代表的简单情况的解决方案。

        带有键/值对数组的数组,a2

        使用[key,value] 类型数组的数组,有两种方法。

        首先,Hash::[] 仍然有效(就像 *a1 一样):

        Hash[a2] # => {"apple"=>1, "banana"=>2}
        

        然后#to_h 现在也可以使用了:

        a2.to_h  # => {"apple"=>1, "banana"=>2}
        

        所以,对于简单的嵌套数组情况,有两个简单的答案。

        即使将子数组作为键或值也是如此,就像a3

        Hash[a3] # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "pear"=>[4, 5]} 
        a3.to_h  # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "pear"=>[4, 5]}
        

        但榴莲有尖刺(异常结构会产生问题):

        如果我们得到不平衡的输入数据,我们会遇到#to_h 的问题:

        a4.to_h  # => ArgumentError: wrong array length at 3 (expected 2, was 1)
        

        Hash::[] 仍然有效,只需将nil 设置为durian 的值(以及a4 中的任何其他数组元素,它只是一个1 值数组):

        Hash[a4] # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "durian"=>nil}
        

        扁平化 - 使用新变量 a5a6

        提到了flatten 的其他一些答案,有或没有1 参数,所以让我们创建一些新变量:

        a5 = a4.flatten
        # => ["apple", 1, "banana", 2,  "orange", "seedless" , 3, "durian"] 
        a6 = a4.flatten(1)
        # => ["apple", 1, "banana", 2, ["orange", "seedless"], 3, "durian"] 
        

        我选择使用a4 作为基础数据,因为我们遇到了余额问题,出现a4.to_h。我认为调用flatten 可能是某人尝试解决该问题的一种方法,如下所示。

        flatten 不带参数 (a5):

        Hash[*a5]       # => {"apple"=>1, "banana"=>2, "orange"=>"seedless", 3=>"durian"}
        # (This is the same as calling `Hash[*a4.flatten]`.)
        

        乍一看,这似乎奏效了——但它让我们在无核橙子上走错了路,因此也使3成为durian成为价值

        这和a1 一样,是行不通的:

        a5.to_h # => TypeError: wrong element type String at 0 (expected array)
        

        所以a4.flatten 对我们没有用,我们只想使用Hash[a4]

        flatten(1) 案例 (a6):

        但是只有部分展平呢?值得注意的是,在部分展平的数组 (a6) 上使用 splat 调用 Hash::[] 与调用 Hash[a4]相同的:

        Hash[*a6] # => ArgumentError: odd number of arguments for Hash
        

        预展平数组,仍然嵌套(获取a6 的另一种方式):

        但是如果我们一开始就是这样得到数组的呢? (也就是说,与a1 相比,它是我们的输入数据——只是这次有些数据可以是数组或其他对象。)我们已经看到Hash[*a6] 不起作用,但如果我们仍然想要呢?要获得 最后一个元素(重要!见下文)作为nil 值的键的行为?

        在这种情况下,还有办法做到这一点,使用Enumerable#each_slice 让我们自己回到键/值pairs 作为外部数组中的元素:

        a7 = a6.each_slice(2).to_a
        # => [["apple", 1], ["banana", 2], [["orange", "seedless"], 3], ["durian"]] 
        

        请注意,这最终会得到一个新数组,它不是“identical”到a4,但确实有same values

        a4.equal?(a7) # => false
        a4 == a7      # => true
        

        因此我们可以再次使用Hash::[]

        Hash[a7] # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "durian"=>nil}
        # or Hash[a6.each_slice(2).to_a]
        

        但是有一个问题!

        请务必注意,each_slice(2) 解决方案只有在 last 键是缺少值的情况下才能使事情恢复正常。如果我们稍后添加一个额外的键/值对:

        a4_plus = a4.dup # just to have a new-but-related variable name
        a4_plus.push(['lychee', 4])
        # => [["apple",                1],
        #     ["banana",               2],
        #     [["orange", "seedless"], 3], # multi-value key
        #     ["durian"],              # missing value
        #     ["lychee", 4]]           # new well-formed item
        
        a6_plus = a4_plus.flatten(1)
        # => ["apple", 1, "banana", 2, ["orange", "seedless"], 3, "durian", "lychee", 4]
        
        a7_plus = a6_plus.each_slice(2).to_a
        # => [["apple",                1],
        #     ["banana",               2],
        #     [["orange", "seedless"], 3], # so far so good
        #     ["durian",               "lychee"], # oops! key became value!
        #     [4]]                     # and we still have a key without a value
        
        a4_plus == a7_plus # => false, unlike a4 == a7
        

        我们从中得到的两个哈希值在重要方面是不同的:

        ap Hash[a4_plus] # prints:
        {
                             "apple" => 1,
                            "banana" => 2,
            [ "orange", "seedless" ] => 3,
                            "durian" => nil, # correct
                            "lychee" => 4    # correct
        }
        
        ap Hash[a7_plus] # prints:
        {
                             "apple" => 1,
                            "banana" => 2,
            [ "orange", "seedless" ] => 3,
                            "durian" => "lychee", # incorrect
                                   4 => nil       # incorrect
        }
        

        (注意:我使用awesome_printap 只是为了更容易在此处显示结构;对此没有概念上的要求。)

        所以each_slice 对不平衡平坦输入的解决方案只有在不平衡位位于最后时才有效。


        要点:

        1. 尽可能将这些内容的输入设置为[key, value] 对(外部数组中每个项目的子数组)。
        2. 如果您确实可以这样做,#to_hHash::[] 都可以。
        3. 如果您做不到,Hash::[] 与 splat (*) 结合使用,只要输入平衡
        4. 使用 unbalancedflat 数组作为输入,唯一合理的方法是如果 last value item 是唯一缺少的。

        旁注:我发布这个答案是因为我觉得有一些价值需要补充——一些现有的答案有不正确的信息,而且(我读过的)没有一个像我一样给出完整的答案努力做到这里。我希望它会有所帮助。尽管如此,我还是要感谢那些在我之前的人,他们中的一些人为本答案的部分内容提供了灵感。

        【讨论】:

          【解决方案8】:

          附加到答案但使用匿名数组和注释:

          Hash[*("a,b,c,d".split(',').zip([1,2,3,4]).flatten)]
          

          从内部开始分解答案:

          • "a,b,c,d" 实际上是一个字符串。
          • split 以逗号分隔成一个数组。
          • zip 连同下面的数组。
          • [1,2,3,4] 是一个实际的数组。

          中间结果是:

          [[a,1],[b,2],[c,3],[d,4]]
          

          flatten 然后将其转换为:

          ["a",1,"b",2,"c",3,"d",4]
          

          然后:

          *["a",1,"b",2,"c",3,"d",4] 将其展开为 "a",1,"b",2,"c",3,"d",4

          我们可以将其用作Hash[] 方法的参数:

          Hash[*("a,b,c,d".split(',').zip([1,2,3,4]).flatten)]
          

          产生:

          {"a"=>1, "b"=>2, "c"=>3, "d"=>4}
          

          【讨论】:

          • 这也适用于没有 splat (*) 和扁平化:Hash[("a,b,c,d".split(',').zip([1,2,3,4]))] => {"a"=>1, "b"=>2, "c"=>3, "d"=>4}。我添加的答案中有更多详细信息。
          【解决方案9】:

          如果你有这样的数组 -

          data = [["foo",1,2,3,4],["bar",1,2],["foobar",1,"*",3,5,:foo]]
          

          并且您希望每个数组的第一个元素成为哈希的键,其余元素成为值数组,那么您可以执行以下操作 -

          data_hash = Hash[data.map { |key| [key.shift, key] }]
          
          #=>{"foo"=>[1, 2, 3, 4], "bar"=>[1, 2], "foobar"=>[1, "*", 3, 5, :foo]}
          

          【讨论】:

            【解决方案10】:

            不确定这是否是最好的方法,但这是可行的:

            a = ["apple", 1, "banana", 2]
            m1 = {}
            for x in (a.length / 2).times
              m1[a[x*2]] = a[x*2 + 1]
            end
            
            b = [["apple", 1], ["banana", 2]]
            m2 = {}
            for x,y in b
              m2[x] = y
            end
            

            【讨论】:

              【解决方案11】:

              关于性能和内存分配问题,请查看my answerRails mapping array of hashes onto single hash,我在其中对几种解决方案进行了基准测试。

              reduce / inject 可能是最快或最慢的解决方案,具体取决于您使用哪种方法。

              【讨论】:

                【解决方案12】:

                如果数值是 seq 索引,那么我们可以有更简单的方法... 这是我提交的代码,我的 Ruby 有点生疏了

                   input = ["cat", 1, "dog", 2, "wombat", 3]
                   hash = Hash.new
                   input.each_with_index {|item, index|
                     if (index%2 == 0) hash[item] = input[index+1]
                   }
                   hash   #=> {"cat"=>1, "wombat"=>3, "dog"=>2}
                

                【讨论】:

                  猜你喜欢
                  • 2010-10-19
                  • 2010-12-11
                  • 2010-10-21
                  • 1970-01-01
                  • 2018-03-20
                  • 1970-01-01
                  • 2021-01-17
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多