从 Ruby 中的嵌套哈希和数组中提取值

Extracting value from nested hashes and arrays in Ruby

我有一个看起来像这样的散列:

h = { 
  a: [ ["c", "1"],["d","2"],["e","3"],["f","4"] ], 
  b: [ ["g","5"],["h","6"],["i","7"],["j","8"] ], 
  c: [ ["k","9"],["l","10"],["m","11"],["n","12"] ]
}

从中提取数字以使其看起来像这样的最佳方法是什么?

[1,2,3,4,5,6,7,8,9,10,11,12]

我尝试了一些不同的方法,但它总是需要一个外部数组,我必须从 each 命令链中将其推入。

对数字 \d 使用 flattenselectregexp 的组合:

=> a = { 
  a: [ ["c", "1"],["d","2"],["e","3"],["f","4"] ], 
  b: [ ["g","5"],["h","6"],["i","7"],["j","8"] ], 
  c: [ ["k","9"],["l","10"],["m","11"],["n","12"] ]
}
=> a.values.flatten.select { |x| x =~ /\d/ }.map(&:to_i)
#> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

其他方式 flat_mapmap( ):

reach inside the structure with parentheses to make things more explicit

=> a = { 
  a: [ ["c", "1"],["d","2"],["e","3"],["f","4"] ], 
  b: [ ["g","5"],["h","6"],["i","7"],["j","8"] ], 
  c: [ ["k","9"],["l","10"],["m","11"],["n","12"] ]
}
=> a.flat_map { |_, (n, z, i, x)| [n, z, i, x] }.map { |_, i| i.to_i }
#> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

这可以用正则表达式来完成。

hash.values.flatten.select { |v| v.match(/\d/) }.map(&:to_i)
  • 要获取值,请使用 values 方法

  • 要使数组成为一维数组,请使用flatten方法。

  • 要过滤,请使用 select 方法,并找到与数字正则表达式匹配的字符串。

  • 最后映射这个数组,将元素转换为整数。

代码

def pull_numbers(h)
  h.values.flat_map { |a| a.map { |_,e| Integer(e) } }
end

例子

您的哈希,h[:a][0][0] 稍作修改:

h = { 
  a: [["8c", "1"],["d","2"],["e","3"],["f","4"]], 
  b: [["g","5"],["h","6"],["i","7"],["j","8"]], 
  c: [["k","9"],["l","10"],["m","11"],["n","12"]]
}

pull_numbers(h)
  #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

说明

步骤,如上例:

c = h.values
  #=> [[["8c", "1"], ["d", "2"], ["e", "3"], ["f", "4"]],
  #    [["g", "5"], ["h", "6"], ["i", "7"], ["j", "8"]],
  #    [["k", "9"], ["l", "10"], ["m", "11"], ["n", "12"]]]    

Enumerable#flat_map 传递 c 的第一个元素并设置块变量 a:

a = [["8c", "1"],["d","2"],["e","3"],["f","4"]]

然后:

a.map { |_,e| Integer(e) }
  #=> [1, 2, 3, 4]

我选择使用 Integer(e) 而不是 e.to_i,以便在 e 不是整数的字符串表示时引发异常:

Integer("cat")
  #=> ArgumentError: invalid value for Integer(): "cat"

鉴于:

"cat".to_i
   #=> 0

实际上,Integer 在进行转换之前执行数据检查。

c的其他两个元素处理类似。

变体

可以这样写:

def pull_numbers(h)
  h.values.flatten.each_slice(2).map { |_,e| Integer(e) }
end

我会做:

h.values.flatten(1).map{|x,y| y.to_i }

这是一种方法:

h = { 
  a: [ ["c", "1"],["d","2"],["e","3"],["f","4"] ], 
  b: [ ["g","5"],["h","6"],["i","7"],["j","8"] ], 
  c: [ ["k","9"],["l","10"],["m","11"],["n","12"] ]
}

h.values.flatten(1).collect(&:last).map(&:to_i)
#=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

或者您可以这样做:

h.to_a.flatten.select { |x| x =~ /\d/ }.map(&:to_i)

使用 Array#transpose 方法的简短替代方法:

> h.values.flatten(1).transpose.last
=> ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"]

# with to number conversion
> h.values.flatten(1).transpose.last.map(&:to_i)
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

基准测试

require 'benchmark'

h = {
  a: [ ["c", "1"],["d","2"],["e","3"],["f","4"] ],  
  b: [ ["g","5"],["h","6"],["i","7"],["j","8"] ], 
  c: [ ["k","9"],["l","10"],["m","11"],["n","12"] ] 
}   

Benchmark.bm(10) do |x|
  x.report("transpose") do
    1000.times { h.values.flatten(1).transpose.last.map(&:to_i) }
  end
  x.report("collect/map") do
    1000.times { h.values.flatten(1).collect(&:last).map(&:to_i) }
  end
  x.report("regexp") do
    1000.times { h.values.flatten.select { |v| v.match(/\d/) }.map(&:to_i) }
  end
  x.report("Integer") do
    1000.times { h.values.flat_map { |a| a.map { |_,e| Integer(e) } } }
  end
end

结果

                 user     system      total        real
transpose    0.000000   0.000000   0.000000 (  0.006971)
collect/map  0.010000   0.000000   0.010000 (  0.007490)
regexp       0.030000   0.010000   0.040000 (  0.031939)
Integer      0.010000   0.000000   0.010000 (  0.006832)