Conditional key/value in a ruby hash - ruby

Is there a nice (one line) way of writing a hash in ruby with some entry only there if a condition is fulfilled? I thought of
{:a => 'a', :b => ('b' if condition)}
But that leaves :b == nil if the condition is not fulfilled. I realize this could be done easily in two lines or so, but it would be much nicer in one line (e.g. when passing the hash to a function).
Am I missing (yet) another one of ruby's amazing features here? ;)

UPDATE Ruby 2.4+
Since ruby 2.4.0, you can use the compact method:
{ a: 'a', b: ('b' if cond) }.compact
Original answer (Ruby 1.9.2)
You could first create the hash with key => nil for when the condition is not met, and then delete those pairs where the value is nil. For example:
{ :a => 'a', :b => ('b' if cond) }.delete_if{ |k,v| v.nil? }
yields, for cond == true:
{:b=>"b", :a=>"a"}
and for cond == false
{:a=>"a"}
UPDATE for ruby 1.9.3
This is equivalent - a bit more concise and in ruby 1.9.3 notation:
{ a: 'a', b: ('b' if cond) }.reject{ |k,v| v.nil? }

From Ruby 1.9+, if you want to build a hash based on conditionals you can use tap, which is my new favourite thing. This breaks it onto multiple lines but is more readable IMHO:
{}.tap do |my_hash|
my_hash[:a] = 'a'
my_hash[:b] = 'b' if condition
end

>= Ruby 2.4:
{a: 'asd', b: nil}.compact
=> {:a=>"asd"}

Interested in seeing other answers, but this is the best I can think up of for a one-liner (I'm also notoriously bad at one-liners :P)
{:a => 'a'}.merge( condition ? {:b => 'b'} : {} )

There's a lot of clever solutions in here, but IMO the simplest and therefore best approach is
hash = { a: 'a', b: 'b' }
hash[:c] = 'c' if condition
It goes against the OP's request of doing it in two lines, but really so do the other answers that only appear to be one-liners. Let's face it, this is the most trivial solution and it's easy to read.

In Ruby 2.0 there is a double-splat operator (**) for hashes (and keyword parameters) by analogy to the old splat operator (*) for arrays (and positional parameters). So you could say:
{a: 'b', **(condition ? {b: 'b'} : {})}

Hash[:a, 'a', *([:b, 'b'] if condition1), *([:c, 'c'] if condition2)]
This relies on the fact that *nil expands to vacuity in ruby 1.9. In ruby 1.8, you might need to do:
Hash[:a, 'a', *(condition1 ? [:b, 'b'] : []), *(condition2 ? [:c, 'c'] : [])]
or
Hash[:a, 'a', *([:b, 'b'] if condition1).to_a, *([:c, 'c'] if condition2).to_a]

If you have multiple conditions and logic that others will need to understand later then I suggest this is not a good candidate for a 1 liner. It would make more sense to properly create your hash based on the required logic.

This one is nice for multiple conditionals.
(
hash = {:a => 'a'}.tap {|h|
h.store( *[(:b if condition_b), 'b'] )
h.store( *[(:c if condition_c), 'c'] )
}
).delete(nil)
Note that I chose nil as the "garbage" key, which gets deleted when you're done. If you ever need to store a real value with a nil key, just change the store conditionals to something like:
(condition_b ? :b : garbage_key)
then delete(garbage_key) at the end.
This solution will also keep existing nil values intact, e.g. if you had :a => nil in the original hash, it won't be deleted.

My one-liner solution:
{:a => 'a'}.tap { |h| h.merge!(:b => 'b') if condition }

hash, hash_new = {:a => ['a', true], :b => ['b', false]}, {}
hash.each_pair{|k,v| hash_new[k] = v[1] ? v : nil }
puts hash_new

eval("{:a => 'a' #{', :b => \'b\'' if condition }}")
or even
eval("{#{[":a => 'a'", (":b=>'b'" if ax)].compact.join(',')}}")
for more simple add conditions

Related

Create hash with keys from array and a standard value

I have an array like this:
['a', 'b', 'c']
What is the simplest way to turn it into:
{'a' => true, 'b' => true, 'c' => true}
true is just a standard value that values should hold.
How about below ?
2.1.0 :001 > ['a', 'b', 'c'].each_with_object(true).to_h
=> {"a"=>true, "b"=>true, "c"=>true}
Try:
Hash[ary.map {|k| [k, true]}]
Since Ruby 2.0 you can use to_h method:
ary.map {|k| [k, true]}.to_h
Depending on your specific needs, maybe you do not actually need to initialize the values. You could simply create a Hash with a default value of true this way:
h = Hash.new(true)
#=> {}
Then, when you try to access a key that was not present before:
h['a']
#=> true
h['b']
#=> true
Pros: less memory used, faster to initialize.
Cons: does not actually store keys so the hash will be empty until some other code stores values in it. This will only be a problem if your program relies on reading the keys from the hash or wants to iterate over the hash.
['a', 'b', 'c'].each_with_object({}) { |key, hash| hash[key] = true }
Another one
> Hash[arr.zip Array.new(arr.size, true)]
# => {"a"=>true, "b"=>true, "c"=>true}
You can also use Array#product:
['a', 'b', 'c'].product([true]).to_h
#=> {"a"=>true, "b"=>true, "c"=>true}
Following code will do this:
hash = {}
['a', 'b', 'c'].each{|i| hash[i] = true}
Hope this helps :)
If you switch to Python, it's this easy:
>>> l = ['a', 'b', 'c']
>>> d = dict.fromkeys(l, True)
>>> d
{'a': True, 'c': True, 'b': True}

Getting an array of hash values given specific keys

Given certain keys, I want to get an array of values from a hash (in the order I gave the keys). I had done this:
class Hash
def values_for_keys(*keys_requested)
result = []
keys_requested.each do |key|
result << self[key]
end
return result
end
end
I modified the Hash class because I do plan to use it almost everywhere in my code.
But I don't really like the idea of modifying a core class. Is there a builtin solution instead? (couldn't find any, so I had to write this).
You should be able to use values_at:
values_at(key, ...) → array
Return an array containing the values associated with the given keys. Also see Hash.select.
h = { "cat" => "feline", "dog" => "canine", "cow" => "bovine" }
h.values_at("cow", "cat") #=> ["bovine", "feline"]
The documentation doesn't specifically say anything about the order of the returned array but:
The example implies that the array will match the key order.
The standard implementation does things in the right order.
There's no other sensible way for the method to behave.
For example:
>> h = { :a => 'a', :b => 'b', :c => 'c' }
=> {:a=>"a", :b=>"b", :c=>"c"}
>> h.values_at(:c, :a)
=> ["c", "a"]
i will suggest you do this:
your_hash.select{|key,value| given_keys.include?(key)}.values

rails-settings: NoMethodError: undefined method `merge' for []:Array

I'm trying to implement the rails-settings gem (https://github.com/100hz/rails-settings) into my Rails 3 project using Ruby 1.8.7
Setting and retrieving the settings works perfectly, but I get an error if I try getting all settings of a specific user.
So, in the 'rails console' the following works:
user = User.find(123)
user.settings.color = :red
user.settings.color
But if I try to get all settings:
user.settings.all
I get:
NoMethodError: undefined method `merge' for []:Array
from /[...]/.rvm/gems/ruby-1.8.7-p334/bundler/gems/rails-settings-883114dfd933/lib/rails-settings/settings.rb:55:in `all'
from (irb):5
line 55 in the settings.rb:
#retrieve all settings as a hash (optionally starting with a given namespace)
def self.all(starting_with=nil)
options = starting_with ? { :conditions => "var LIKE '#{starting_with}%'"} : {}
vars = thing_scoped.find(:all, {:select => 'var, value'}.merge(options))
result = {}
vars.each do |record|
result[record.var] = record.value
end
# line 55 is below this one...
##defaults.select{ |k| k =~ /^#{starting_with}/ }.merge(result).with_indifferent_access
end
Whats the problem here? Or is this a ruby 1.8.7 vs. 1.9.2 thing?
That's a Ruby 1.8.7 vs. 1.9.2 thing
The Hash select method under ruby 1.8.7 will return an Array of Arrays.
Example:
{:a => 'a', :b => 'b', :c => 'c'}.select {|k, v| v > 'a'} #=> [[:b,'b'],[:c,'c']]
While the same thing running Ruby 1.9.2 will return:
{:a => 'a', :b => 'b', :c => 'c'}.select {|k, v| v > 'a'} #=> {:b => 'b',:c => 'c'}
You will need to post process the result and turn it into a hsah again or use something like inject.
Edit:
Here is a quick/ugly example of the inject
{:a => 'a', :b => 'b', :c => 'c'}.inject({}) {|r, e| e[1] > 'a' ? r.merge({e[0] => e[1]}) : r }
Semantically speaking:
collection.inject(container) { |container, element| select_condition ? container + element : container }
Edit 2: (Based on #CaleyWoods post)
Hash[*##defaults.select{ |k,v| k =~ /^#{starting_with}/ }.flatten].merge(result)
The |k, v| will prevent unnecessary warnings.
This looks like it's trying to do a merge on an Array which is not a method the Array class in Ruby. Merge is supported for Hash, it looks like your returned object is not the correct type. The author was definitely relying on a hash, in the next to last line 'with_indifferent_access' is called which is trying to allow you to select items from the hash with strings or symbols.
I can't examine the gem further right now and I wish I could provide a more helpful answer. If this hasn't been answered later i'll come back and help you out.
Not sure why the author is using double and single quotes in options and vars. He's also trying to populate the hash by hand instead of with inject. It's not awful by any means but I think there's room for improvement in the small bit of code you posted from the file.

ruby Hash include another hash, deep check

what is the best way to make such deep check:
{:a => 1, :b => {:c => 2, :f => 3, :d => 4}}.include?({:b => {:c => 2, :f => 3}}) #=> true
thanks
I think I see what you mean from that one example (somehow). We check to see if each key in the subhash is in the superhash, and then check if the corresponding values of these keys match in some way: if the values are hashes, perform another deep check, otherwise, check if the values are equal:
class Hash
def deep_include?(sub_hash)
sub_hash.keys.all? do |key|
self.has_key?(key) && if sub_hash[key].is_a?(Hash)
self[key].is_a?(Hash) && self[key].deep_include?(sub_hash[key])
else
self[key] == sub_hash[key]
end
end
end
end
You can see how this works because the if statement returns a value: the last statement evaluated (I did not use the ternary conditional operator because that would make this far uglier and harder to read).
I like this one:
class Hash
def include_hash?(other)
other.all? do |other_key_value|
any? { |own_key_value| own_key_value == other_key_value }
end
end
end

What is the meaning of grep on a Hash?

{'a' => 'b'}.grep /a/
=> []
>> {'a' => 'b'}.grep /b/
=> []
It doesn't seem to match the keys or values. Does it do something I'm not discerning?
grep is defined on Enumerable, i.e. it is a generic method that doesn't know anything about Hashes. It operates on whatever the elements of the Enumerable are. Ruby doesn't have a type for key-value-pairs, it simply represents Hash entries as two-element arrays where the first element is the key and the second element is the value.
grep uses the === method to filter out elements. And since neither
/a/ === ['a', 'b']
nor
/b/ === ['a', 'b']
are true, you always get an empty array as response.
Try this:
def (t = Object.new).===(other)
true
end
{'a' => 'b'}.grep t
# => [['a', 'b']]
Here you can see how grep works with Hashes.

Resources