Ruby:过滤哈希键的最简单方法?

我有一个这样的散列:

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。它变化。

我如何用单词选择和后面的一个或多个数字来挑选键?

奖金:

将散列转换为以tab (\t)作为分隔符的字符串。我这样做了,但它花了几行代码。通常大师级的卢比手可以在一行或几行内完成。

259622 次浏览

Hash::select:

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

编辑到原始答案:即使这是答案(截至此评论的时间)是选定的答案,这个答案的原始版本已经过时。

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

正如另一个答案所提到的,Ruby >= 2.5添加了Hash#slice方法,这个方法以前只在Rails中可用。

例子:

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

下面是最初的答案,我猜如果你是Ruby <没有Rails的2.5,尽管我想这种情况在这一点上是相当不常见的。


如果你正在使用Ruby,你可以使用select方法。您需要将键从Symbol转换为String来进行regexp匹配。这将给你一个只有选项的新哈希。

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]

如果你想要剩下的哈希值:

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

或者如果你只是想要钥匙:

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

关于奖金问题:

  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. If you have output from #delete_if method like this (hash):

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

    然后:

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

把它放到初始化式中

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"}

在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}
params.select{ |k,v| k =~ /choice\d/ }.map{ |k,v| v}.join("\t")
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"}

最简单的方法是包含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"}

我认为声明其他可能有bug的函数是不值得的,最好使用在过去几年里调整过的方法。

这是一条解决完整原始问题的直线:

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

但上面的大多数解决方案都是在解决需要提前知道键的情况,使用slice或简单的regexp。

下面是另一种方法这既适用于简单的用例,也适用于更复杂的用例,这些用例在运行时是可切换的

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

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

解决原问题:

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"

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

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

如果你使用rails并且键在一个单独的列表中,你可以使用*符号:

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

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

我有一个类似的问题,在我的情况下,解决方案是一行,即使键不是符号也能工作,但你需要在数组中有标准键

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"}

散列片

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


# If you have an array of keys you want to limit to, you should splat them:
valid_keys = [:mass, :velocity, :time]
search(options.slice(*valid_keys))