【问题标题】:Array to Hash Ruby数组散列 Ruby
【发布时间】:2011-05-01 00:14:46
【问题描述】:

转换这个数组:

a = ["item 1", "item 2", "item 3", "item 4"] 

...到一个哈希:

{ "item 1" => "item 2", "item 3" => "item 4" }

even 索引处的元素是 keysodd 元素是 values

【问题讨论】:

    标签: ruby arrays hashmap


    【解决方案1】:
    a = ["item 1", "item 2", "item 3", "item 4"]
    h = Hash[*a] # => { "item 1" => "item 2", "item 3" => "item 4" }
    

    就是这样。 * 称为 splat 运算符。

    @Mike Lewis 的一个警告(在 cmets 中):“对此要非常小心。Ruby 会扩展堆栈上的 splats。如果您使用大型数据集执行此操作,预计会炸毁您的堆栈。”

    因此,对于大多数一般用例来说,这种方法很好,但如果您想对大量数据进行转换,请使用不同的方法。例如,@Łukasz Niemier(也在 cmets 中)为大型数据集提供了这种方法:

    h = Hash[a.each_slice(2).to_a]
    

    【讨论】:

    • @tester,* 被称为 splat 运算符。它接受一个数组并将其转换为项目的文字列表。所以*[1,2,3,4] => 1, 2, 3, 4。在这个例子中,上面相当于做Hash["item 1", "item 2", "item 3", "item 4"]。而Hash 有一个[] 方法,它接受参数列表(使偶数索引键和奇数索引值),但Hash[] 不接受数组,所以我们使用* 分解数组。
    • 对此要非常小心。 Ruby 扩展堆栈上的 splats。如果您使用大型数据集执行此操作,预计会耗尽您的堆栈。
    • 在大数据表上可以使用Hash[a.each_slice(2).to_a]
    • “爆仓”是什么意思?
    • @Kevin,stack 使用程序分配和保留用于某些特定操作的一小块内存。最常见的是,它用于保存迄今为止已调用的方法的堆栈。这就是术语堆栈跟踪的由来,这也是为什么无限递归方法会导致堆栈溢出。这个答案中的方法也使用了栈,但是由于栈只是内存的一小块区域,如果你用一个大数组尝试这个方法,它会填满栈并导致错误(与堆栈溢出)。
    【解决方案2】:

    Ruby 2.1.0 在 Array 上引入了 to_h 方法,如果您的原始数组由键值对数组组成,则该方法可以满足您的要求:http://www.ruby-doc.org/core-2.1.0/Array.html#method-i-to_h

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

    【讨论】:

    • 漂亮!比这里的其他一些解决方案要好得多。
    • 对于 2.1.0 之前的 ruby​​ 版本,您可以使用 Hash::[] 方法获得类似的结果,只要您有一对嵌套数组即可。所以 a =[[:foo, :1], [bar, 2]] --- Hash[a] => {:foo=>1, :bar=>2}
    • @AfDev,确实,谢谢。你是对的(忽略小错别字:bar 需要是一个符号,而符号:2 应该是一个整数。所以,你的表达式更正为a = [[:foo, 1], [:bar, 2]])。
    【解决方案3】:

    只需将Hash.[] 与数组中的值一起使用。例如:

    arr = [1,2,3,4]
    Hash[*arr] #=> gives {1 => 2, 3 => 4}
    

    【讨论】:

    • [*arr] 是什么意思?
    • @Marius: *arrarr 转换成一个参数列表,所以这是以arr 的内容作为参数调用Hash 的[] 方法。
    【解决方案4】:

    或者如果你有一个[key, value]数组,你可以这样做:

    [[1, 2], [3, 4]].inject({}) do |r, s|
      r.merge!({s[0] => s[1]})
    end # => { 1 => 2, 3 => 4 }
    

    【讨论】:

    • 您的回答与问题无关,在您的情况下,使用相同的Hash[*arr] 仍然容易得多
    • 不。它将返回{ [1, 2] => [3, 4] }。而且由于问题的标题是“Array to Hash”,而内置的“Hash to Array”方法确实如此:{ 1 => 2, 3 => 4}.to_a # => [[1, 2], [3, 4]],我想不止一个人可以在这里尝试得到内置的“Hash to Array”的倒数“ 方法。其实,反正我就是这样结束的。
    • 抱歉,我添加了一个备用星号。 Hash[arr] 将为您完成这项工作。
    • 恕我直言更好的解决方案:Hash[*array.flatten(1)]
    • Yossi:很抱歉让死人复活,但他的回答还有一个更大的问题,那就是使用#inject 方法。对于#merge!,应该使用#each_with_object。如果坚持使用#inject,则应该使用#merge 而不是#merge!
    【解决方案5】:

    这是我在谷歌上搜索时所寻找的:

    [{a: 1}, {b: 2}].reduce({}) { |h, v| h.merge v } => {:a=>1, :b=>2}

    【讨论】:

    • 您不想使用merge,它会在每次循环迭代时构造并丢弃一个新的哈希值,而且速度非常慢。如果您有一个哈希数组,请尝试[{a:1},{b:2}].reduce({}, :merge!) - 它会将所有内容合并到相同的(新)哈希中。
    • 谢谢,这也是我想要的! :)
    • 你也可以.reduce(&:merge!)
    • [{a: 1}, {b: 2}].reduce(&:merge!) 计算结果为 {:a=>1, :b=>2}
    • 之所以有效,是因为注入/减少具有可以省略参数的功能,在这种情况下,它使用数组的第一个参数来操作 as 输入参数,并将数组的其余部分作为数组。将它与 symbol-to-proc 结合起来,你就得到了这个简洁的结构。换句话说,[{a: 1}, {b: 2}].reduce(&:merge!)[{a: 1}, {b: 2}].reduce { |m, x| m.merge(x) } 相同,[{b: 2}].reduce({a: 1}) { |m, x| m.merge(x) }[{b: 2}].reduce({a: 1}) { |m, x| m.merge(x) } 相同。
    【解决方案6】:

    Enumerator 包括Enumerable。由于2.1Enumerable也有一个方法#to_h。这就是为什么,我们可以这样写:-

    a = ["item 1", "item 2", "item 3", "item 4"]
    a.each_slice(2).to_h
    # => {"item 1"=>"item 2", "item 3"=>"item 4"}
    

    因为#each_slice没有block给了我们Enumerator,按照上面的解释,我们可以在Enumerator对象上调用#to_h方法。

    【讨论】:

      【解决方案7】:

      你可以这样尝试,对于单个数组

      irb(main):019:0> a = ["item 1", "item 2", "item 3", "item 4"]
        => ["item 1", "item 2", "item 3", "item 4"]
      irb(main):020:0> Hash[*a]
        => {"item 1"=>"item 2", "item 3"=>"item 4"}
      

      对于数组的数组

      irb(main):022:0> a = [[1, 2], [3, 4]]
        => [[1, 2], [3, 4]]
      irb(main):023:0> Hash[*a.flatten]
        => {1=>2, 3=>4}
      

      【讨论】:

        【解决方案8】:
        a = ["item 1", "item 2", "item 3", "item 4"]
        Hash[ a.each_slice( 2 ).map { |e| e } ]
        

        或者,如果你讨厌Hash[ ... ]

        a.each_slice( 2 ).each_with_object Hash.new do |(k, v), h| h[k] = v end
        

        或者,如果你是一个破函数式编程的懒惰粉丝:

        h = a.lazy.each_slice( 2 ).tap { |a|
          break Hash.new { |h, k| h[k] = a.find { |e, _| e == k }[1] }
        }
        #=> {}
        h["item 1"] #=> "item 2"
        h["item 3"] #=> "item 4"
        

        【讨论】:

        • 如果您不完全讨厌Hash[ ... ],但想将其用作链式方法(就像您可以使用to_h 一样),您可以结合Boris 的建议并写:arr.each_slice( 2 ).map { |e| e }.tap { |a| break Hash[a] }跨度>
        • 为了使上面代码的语义更清晰:这将创建一个“惰性哈希”h,它最初是,并且会拉取需要时从原始数组 a 中取出元素。只有这样它们才会真正存储在 h!
        【解决方案9】:

        所有答案都假定起始数组是唯一的。 OP 没有指定如何处理具有重复条目的数组,这会导致重复键。

        我们来看看:

        a = ["item 1", "item 2", "item 3", "item 4", "item 1", "item 5"]
        

        您将丢失 item 1 => item 2 对,因为它被覆盖 bij item 1 => item 5

        Hash[*a]
        => {"item 1"=>"item 5", "item 3"=>"item 4"}
        

        包括reduce(&:merge!) 在内的所有方法都会导致相同的删除。

        不过,这可能正是您所期望的。但在其他情况下,您可能希望得到一个带有Array 值的结果:

        {"item 1"=>["item 2", "item 5"], "item 3"=>["item 4"]}
        

        天真的方法是创建一个辅助变量,一个具有默认值的哈希,然后将其填充到循环中:

        result = Hash.new {|hash, k| hash[k] = [] } # Hash.new with block defines unique defaults.
        a.each_slice(2) {|k,v| result[k] << v }
        a
        => {"item 1"=>["item 2", "item 5"], "item 3"=>["item 4"]}
        

        也许可以在一行中使用assocreduce 完成上述操作,但这变得更加难以推理和阅读。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2018-12-28
          • 1970-01-01
          • 2011-07-24
          • 2023-03-11
          • 2014-08-31
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多