I have a hash that looks something like this:
params = { :irrelevant => "A String",
:choice1 => "Oh look, another one",
:choice2 => "Even more strings",
:choice3 => "But wait",
:irrelevant2 => "The last string" }
And I want a simple way to reject all the keys that aren't choice+int. It could be choice1, or choice1 through choice10. It varies.
How do I single out the keys with just the word choice and a digit or digits after them?
Bonus:
Turn the hash into a string with tab (\t) as a delimiter. I did this, but it took several lines of code. Usually master Rubicians can do it in one or so lines.
This question is related to
ruby
The easiest way is to include the gem 'activesupport' (or gem 'active_support').
params.slice(:choice1, :choice2, :choice3)
I had a similar problem, in my case the solution was a one liner which works even if the keys aren't symbols, but you need to have the criteria keys in an array
criteria_array = [:choice1, :choice2]
params.select { |k,v| criteria_array.include?(k) } #=> { :choice1 => "Oh look another one",
:choice2 => "Even more strings" }
Another example
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"}
This is a one line to solve the complete original question:
params.select { |k,_| k[/choice/]}.values.join('\t')
But most the solutions above are solving a case where you need to know the keys ahead of time, using slice
or simple regexp.
Here is another approach that works for simple and more complex use cases, that is swappable at runtime
data = {}
matcher = ->(key,value) { COMPLEX LOGIC HERE }
data.select(&matcher)
Now not only this allows for more complex logic on matching the keys or the values, but it is also easier to test, and you can swap the matching logic at runtime.
Ex to solve the original issue:
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"
With Hash::select
:
params = params.select { |key, value| /^choice\d+$/.match(key.to_s) }
As for bonus question:
If you have output from #select
method like this (list of 2-element arrays):
[[:choice1, "Oh look, another one"], [:choice2, "Even more strings"], [:choice3, "But wait"]]
then simply take this result and execute:
filtered_params.join("\t")
# or if you want only values instead of pairs key-value
filtered_params.map(&:last).join("\t")
If you have output from #delete_if
method like this (hash):
{:choice1=>"Oh look, another one", :choice2=>"Even more strings", :choice3=>"But wait"}
then:
filtered_params.to_a.join("\t")
# or
filtered_params.values.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"}
Put this in an initializer
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
Then you can do
{a: "1", b: "b", c: "c", d: "d"}.filter(:a, :b) # => {a: "1", b: "b"}
or
{a: "1", b: "b", c: "c", d: "d"}.filter(/^a/) # => {a: "1"}
The easiest way is to include the gem 'activesupport'
(or gem 'active_support'
).
Then, in your class you only need to
require 'active_support/core_ext/hash/slice'
and to call
params.slice(:choice1, :choice2, :choice3) # => {:choice1=>"Oh look, another one", :choice2=>"Even more strings", :choice3=>"But wait"}
I believe it's not worth it to be declaring other functions that may have bugs, and it's better to use a method that has been tweaked during last few years.
In Ruby, the Hash#select is a right option. If you work with Rails, you can use Hash#slice and Hash#slice!. e.g. (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")
If you want the remaining hash:
params.delete_if {|k, v| ! k.match(/choice[0-9]+/)}
or if you just want the keys:
params.keys.delete_if {|k| ! k.match(/choice[0-9]+/)}
If you work with rails and you have the keys in a separate list, you can use the *
notation:
keys = [:foo, :bar]
hash1 = {foo: 1, bar:2, baz: 3}
hash2 = hash1.slice(*keys)
=> {foo: 1, bar:2}
As other answers stated, you can also use slice!
to modify the hash in place (and return the erased key/values).
Source: Stackoverflow.com