Ruby:筛选哈希键的最简单方法?


225

我的哈希看起来像这样:

params = { :irrelevant => "A String",
           :choice1 => "Oh look, another one",
           :choice2 => "Even more strings",
           :choice3 => "But wait",
           :irrelevant2 => "The last string" }

我想要一种简单的方法来拒绝所有不是choice + int的键。可以是choice1,也可以是choice1到choice10。不同。

如何仅选择单词并在其后的一个或多个数字中选出键?

奖金:

用制表符(\ t)作为分隔符,将哈希值转换为字符串。我这样做了,但是花了几行代码。通常,高级Rubicians可以在一行左右的时间内完成此操作。


关于红利问题,您能否阐明字符串的外观。
mikej 2011年

当然,上面的示例哈希将产生:“哦,看,另一个\ t甚至更多的字符串\ t但是要等待”(字符串的末尾不是\ t,只能在它们之间)
Derek

您的示例字符串已在注释中被截断。您可以使用编辑链接来编辑问题,并在其中添加示例。您还可以张贴到目前为止想出的红宝石,作为您要实现的目标的一个示例。
mikej 2011年

Answers:


281

编辑原始答案:即使是该答案(截至本评论发表之时)是所选答案,但该答案的原始版本已过时。

我在这里添加了一个更新,以帮助其他人避免像我一样被这个答案所困扰。

正如其他答案提到的那样,Ruby> = 2.5添加了Hash#slice以前仅在Rails中可用的方法。

例:

> { one: 1, two: 2, three: 3 }.slice(:one, :two)
=> {:one=>1, :two=>2}

编辑结束。以下是原始答案,我猜想如果您使用的是Ruby <2.5而没有Rails,这将是很有用的,尽管我认为目前这种情况并不常见。


如果您使用的是Ruby,则可以使用select方法。您需要将键从Symbol转换为String来进行正则表达式匹配。这将为您提供一个包含选择的新哈希。

choices = params.select { |key, value| key.to_s.match(/^choice\d+/) }

或者您可以使用delete_if和修改现有的哈希,例如

params.delete_if { |key, value| !key.to_s.match(/choice\d+/) }

或者,如果只是键而不是所需的值,则可以执行以下操作:

params.keys.select { |key| key.to_s.match(/^choice\d+/) }

这将只给出键的数组,例如 [:choice1, :choice2, :choice3]


1
优秀的!一旦看到它,就这么简单!非常感谢,也感谢其他花时间回答的人。
德里克(Derek)

2
如今,IIRC可以使用正则表达式来解析符号。
安德鲁·格林

@安德鲁,我想你是对的。我在测试此问题的答案时使用的是1.8.7。
mikej 2011年

1
对应的.select.reject,如果这会使您的代码更加惯用。
Joe Atzberger '16

#slice即使没有活动支持,该方法也适用于2.5.3。
Graywolf

305

在Ruby中,Hash#select是一个正确的选项。如果使用Rails,则可以使用Hash#slice和Hash#slice!。例如(rails 3.2.13)

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

h1.slice(:a, :b)         # return {:a=>1, :b=>2}, but h1 is not changed

h2 = h1.slice!(:a, :b)   # h1 = {:a=>1, :b=>2}, h2 = {:c => 3, :d => 4}

12
提防字符串键。.h1= {'a'=> 1,:b => 2}将仅返回{:b => 2}与h1.slice(:a,:b)
davidcollom 2013年

很棒的解决方案。我用它来返回rspec中的结果,我想在其中解析哈希中的哈希值。```test = {:a => 1,:b => 2,c => {:A => 1,:B => 2}} solution ==> test.c.slice(:B)`` `
PriyankaK 2014年

用ActiveSupport代替Rails,这很完美;)
Geoffroy

5
但是,这并不能回答问题,因为他想要一种方法来提取“ choice + int”键的值。您的解决方案只能提取提前知道的密钥。
metakungfu

46

最简单的方法是包含gem 'activesupport'(或gem 'active_support')。

然后,在您的课程中,您只需要

require 'active_support/core_ext/hash/slice'

并致电

params.slice(:choice1, :choice2, :choice3) # => {:choice1=>"Oh look, another one", :choice2=>"Even more strings", :choice3=>"But wait"}

我认为声明其他可能存在错误的函数是不值得的,最好使用过去几年中经过调整的方法。


至少在2.5上不需要Activerecord。
Graywolf

13

最简单的方法是包括gem'activesupport'(或gem'active_support')。

params.slice(:choice1, :choice2, :choice3)


4
请在您的答案中添加更多详细信息。
Mohit Jain

13

如果使用滑轨,并且按键在单独的列表中,则可以使用*表示法:

keys = [:foo, :bar]
hash1 = {foo: 1, bar:2, baz: 3}
hash2 = hash1.slice(*keys)
=> {foo: 1, bar:2}

如其他答案所述,您还可以slice!用来修改哈希值(并返回已擦除的键/值)。


9

这是一条线,可以解决完整的原始问题:

params.select { |k,_| k[/choice/]}.values.join('\t')

但是,以上大多数解决方案都在解决一种情况,您需要使用slice或简单的正则表达式来提前了解密钥。

这是适用于简单和更复杂用例的另一种方法,可在运行时交换

data = {}
matcher = ->(key,value) { COMPLEX LOGIC HERE }
data.select(&matcher)

现在,这不仅允许在匹配键或值时使用更复杂的逻辑,而且还更易于测试,并且您可以在运行时交换匹配逻辑。

Ex解决原始问题:

def some_method(hash, matcher) 
  hash.select(&matcher).values.join('\t')
end

params = { :irrelevant => "A String",
           :choice1 => "Oh look, another one",
           :choice2 => "Even more strings",
           :choice3 => "But wait",
           :irrelevant2 => "The last string" }

some_method(params, ->(k,_) { k[/choice/]}) # => "Oh look, another one\\tEven more strings\\tBut wait"
some_method(params, ->(_,v) { v[/string/]}) # => "Even more strings\\tThe last string"

1
我真的很喜欢匹配器
卡林,2016年


6

如果您想要剩余的哈希值:

params.delete_if {|k, v| ! k.match(/choice[0-9]+/)}

或者,如果您只想要按键:

params.keys.delete_if {|k| ! k.match(/choice[0-9]+/)}

根据选择的choiceX和irrelevantX数量,选择OR或delete_if可能更好。如果您有更多选择,则delete_if更好。
ayckoster 2011年

6

把它放在初始化器中

class Hash
  def filter(*args)
    return nil if args.try(:empty?)
    if args.size == 1
      args[0] = args[0].to_s if args[0].is_a?(Symbol)
      self.select {|key| key.to_s.match(args.first) }
    else
      self.select {|key| args.include?(key)}
    end
  end
end

那你可以做

{a: "1", b: "b", c: "c", d: "d"}.filter(:a, :b) # =>  {a: "1", b: "b"}

要么

{a: "1", b: "b", c: "c", d: "d"}.filter(/^a/)  # =>  {a: "1"}

5
params.select{ |k,v| k =~ /choice\d/ }.map{ |k,v| v}.join("\t")

4

至于奖金问题:

  1. 如果您从#select类似方法的输出(2元素数组的列表):

    [[:choice1, "Oh look, another one"], [:choice2, "Even more strings"], [:choice3, "But wait"]]

    然后只需获取以下结果并执行:

    filtered_params.join("\t")
    # or if you want only values instead of pairs key-value
    filtered_params.map(&:last).join("\t")
  2. 如果您从这样的#delete_if方法(哈希)获得输出:

    {:choice1=>"Oh look, another one", :choice2=>"Even more strings", :choice3=>"But wait"}

    然后:

    filtered_params.to_a.join("\t")
    # or
    filtered_params.values.join("\t")

很好的解决方案。一个问题:是否filtered_pa​​rams.map(&:last).join(“ \ t”)是filtered_pa​​rams.map(| i | i.last).join(“ \ t”)的缩写?如果是这样,什么是“最后”?我可以使用&:value来获取哈希值吗?
德里克(Derek)

map以块为参数。您可以通过&:method构建来动态创建block,这将创建block {|v| v.method }。就我而言,&:last是在数组(2-元素数组)参数上调用的。如果要使用它,请首先检查要接收到的参数进入块(获取1个参数,不要将其解构为多个参数!),然后尝试找到1个方法(不能使用来链接方法&:method),该方法将返回给您想。如果可能的话,您可以使用快捷方式;如果不能,则需要使用全部阻止功能
MBO

2
params = { :irrelevant => "A String",
           :choice1 => "Oh look, another one",
           :choice2 => "Even more strings",
           :choice3 => "But wait",
           :irrelevant2 => "The last string" }

choices = params.select { |key, value| key.to_s[/^choice\d+/] }
#=> {:choice1=>"Oh look, another one", :choice2=>"Even more strings", :choice3=>"But wait"}

0

我有一个类似的问题,在我的情况下,解决方案是一个衬板,即使键不是符号,该衬板也可以工作,但是您需要将标准键排列在数组中

criteria_array = [:choice1, :choice2]

params.select { |k,v| criteria_array.include?(k) } #=> { :choice1 => "Oh look another one",
                                                         :choice2 => "Even more strings" }

另一个例子

criteria_array = [1, 2, 3]

params = { 1 => "A String",
           17 => "Oh look, another one",
           25 => "Even more strings",
           49 => "But wait",
           105 => "The last string" }

params.select { |k,v| criteria_array.include?(k) } #=> { 1 => "A String"}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.