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

3

我有一个看起来像这样的哈希表:

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 命令中推入。


为了好玩,h.to_s.scan(/\d+/).map(&:to_i).sort - Cary Swoveland
将示例输入分配给变量(例如,h = { a:...})很有帮助,这样读者在回答和评论中引用这些变量时无需定义它们。 - Cary Swoveland
6个回答

6

我会这样做:

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

不错,hirolau。就像我所做的那样,最好假设每对中的第一个元素可能包含数字。 - Cary Swoveland
map(&:last).map(&:to_i) 对我来说更易读,虽然有点长。另外根据问题,我看不出你会想要获取一对中的第一个值的原因,所以这个答案得到了我的支持。 - Max
是的,出于某种原因,我不喜欢在彼此之后放置两个映射。也许可以使用.map{|x| x.last.to_i } - hirolau
很棒的解决方案。转置一只是更简洁的:) 谢谢。 - Kocur4d

4
这可以通过正则表达式来实现。
hash.values.flatten.select { |v| v.match(/\d/) }.map(&:to_i)
  • 要获取值,请使用values方法。

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

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

  • 最后,将此数组映射到整数以转换元素。


3

使用flattenselectregexp的组合来处理数字\d

=> 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( )的另一种方式:

使用括号来更明确地访问结构内部

=> 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]

考虑将 x =~ /\d/ 替换为 Integer(x) rescue nil。这允许一对中的第一个元素为 "c8" 或 "8c"(但不是 "8")。你的第二个解决方案过于严格,要求预先知道每对中的第一个值。 - Cary Swoveland
我更喜欢避免使用 raiserescue,因为它们非常慢。第二个解决方案仅适用于机器人 :),而且 rescue 也很容易出现错误,它可能会 rescuex 更重要的东西,例如(有错别字的代码)Intger(x) rescue nil => nil - Roman Kiselenko
也许可以这样写:x =~ /^-?\d+$/ - Cary Swoveland

3

代码

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 的另外两个元素也是类似地处理。

Variant 类型

或者可以这样写:

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

2
这是一种实现方法:

以下是具体步骤:

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)

从效率和可读性的角度来看,h.values不是比h.to_a更优吗? - Cary Swoveland
是的,应该是这样的。我只是尝试了各种方法来完成这个任务。 - Wand Maker

2

使用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)

我喜欢这个解决方案!所有的解决方案都很棒,但是这个最简洁! - Kocur4d
你的结果似乎在更多的键和更大的值数组下保持稳定。我尝试了n = m = 50,其中f = :a; h = { a: hh[:a]*n }; m.times { h[f = f.next] = h[:a] },其中hh是你的哈希表h - Cary Swoveland

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接