Adding a key only to a hash based on an if statement - ruby

Typically, we define a hash as
h={:a=>val1, :b=>val2}
However, i want to add a condition to only add in the key :b if val2 is not a nil value. Something like
h={:a=>val1}
h[:b]=val2 if val2
But can it be encapsulated in a single line?

h = { :a => val1 }.merge(val2 ? { :b => val2 } : {})
But don't do this. Just keep it simple.

You don't have to worry about nil elements in hash, because you can simply clean up hash from them:
{:a => 1, :b => nil}.reject { |k, v| v.nil? } # {:a => 1}

h[:b] = val unless val.nil?

as of ruby 2.4, you can use Hash#compact
h = { a: 1, b: false, c: nil }
h.compact #=> { a: 1, b: false }
h #=> { a: 1, b: false, c: nil }

You could override the []= operator for just that one hash, or make a subclass of Hash and override it there.
hash = {}
class << hash
def []=(key, value)
case key
when :b
raise StandardError("Invalid :b value") if value.nil?
end
super(key,value)
end
end
hash[:a] = 10
hash[:b] = nil # Will raise exception

Related

Ruby hash: return the first key value under which is not nil

Say I have a hash
hash = {a:1, b:false, c:nil}
& a series of keys somewhere: [:c, :b, :a]. Is there a Ruby idiom for returning such a key value under which != nil?
The obv
[:c, :b, :a].select {|key| hash[key] != nil}.first # returns :b
seems too long.
For that I think Enumerable#find might work:
find(ifnone = nil) { |obj| block } → obj or nil
find(ifnone = nil) → an_enumerator
Passes each entry in enum to block. Returns the first for which block
is not false. If no object matches, calls ifnone and returns its
result when it is specified, or returns nil otherwise.
If no block is given, an enumerator is returned instead.
In your case it'd return the first for which block is not nil:
p %i[c b a].find { |key| !{ a: 1, b: nil, c: nil }[key].nil? } # :a
p %i[c b a].find { |key| !{ a: 1, b: 1, c: nil }[key].nil? } # :b
If you want to filter elements with falsy values, you can use the following expressions.
keys = [:d, :c, :b, :a]
hash = { a: 1, b: nil, c: nil, d: 2 }
keys.select(&hash)
# => [:d, :a]
If you want to filter elements with exactly nil as a value, it is not correct, as Mr. Ilya wrote.
You can use detect this will return first value if match.
[:c, :b, :a].detect { |key| hash[key] != nill }. This will return :b.
Hope to help you :D

A hash-like object that acts like a case statement

What is the best way to construct a hash-like class Case, which is initialized by a hash:
cs = Case.new(:a => 1, /b/ => 2, /c/ => 2, /d/ => 3)
and has a method Case#[] that looks up for the first matching key by === (like a case statement) instead of by == (like the conventional hash) and returns the value:
cs["xxb"] => 2
Here's a possibility.
class Case
def initialize(h)
#h = h
end
def [](key,order=:PRE)
case order
when :PRE
h[#h.keys.find { |k| key === k }]
when :POST
h[#h.keys.find { |k| k === key }]
else
# raise exception
end
end
end
cs = Case.new(:a => 1, /b/ => 2, /c/ => 2, [1,2] => "cat", /d/ => 3)
cs["xxb"] #=> nil
cs["xxb",:POST] #=> 2
cs[Regexp] #=> 2
cs[Regexp,:POST] #=> nil
cs[Array] #=> "cat"
cs[Symbol] #=> 1
This assumes h does not have a key nil.
With the understanding that the key in the hash is to come on the left side of ===, the code would be:
class Case
def initialize(h) #h = h end
def [](key) h[#h.keys.find{|k| k === key}] end
end

Is there a better solution to partition a hash into two hashes?

I wrote a method to split a hash into two hashes based on a criteria (a particular hash value). My question is different from another question on Hash. Here is an example of what I expect:
h={
:a => "FOO",
:b => "FOO",
:c => "BAR",
:d => "BAR",
:e => "FOO"
}
h_foo, h_bar = partition(h)
I need h_foo and h_bar to be like:
h_foo={
:a => "FOO",
:b => "FOO",
:e => "FOO"
}
h_bar={
:c => "BAR",
:d => "BAR"
}
My solution is:
def partition h
h.group_by{|k,v| v=="FOO"}.values.collect{|ary| Hash[*ary.flatten]}
end
Is there a clever solution?
There's Enumerable#partition:
h.partition { |k, v| v == "FOO" }.map(&:to_h)
#=> [{:a=>"FOO", :b=>"FOO", :e=>"FOO"}, {:c=>"BAR", :d=>"BAR"}]
Or you could use Enumerable#each_with_object to avoid the intermediate arrays:
h.each_with_object([{}, {}]) { |(k, v), (h_foo, h_bar)|
v == "FOO" ? h_foo[k] = v : h_bar[k] = v
}
#=> [{:a=>"FOO", :b=>"FOO", :e=>"FOO"}, {:c=>"BAR", :d=>"BAR"}]
I don't think there is a clever one liner, but you can make it slightly more generic by doing something like:
def transpose(h,k,v)
h[v] ||= []
h[v] << k
end
def partition(h)
n = {}
h.map{|k,v| transpose(n,k,v)}
result = n.map{|k,v| Hash[v.map{|e| [e, k]}] }
end
which will yield
[{:a=>"FOO", :b=>"FOO", :e=>"FOO"}, {:c=>"BAR", :d=>"BAR"}]
when run against your initial hash h
Edit - TIL about partition. Wicked.
Why not use builtin partition, which is doing almost exactly what you are looking for?
h_foo, h_bar = h.partition { |key, value| value == 'FOO' }
The only downside is that you will get arrays instead of hashes (but you already know how to convert that). In ruby 2.1+ you could simply call .map(&:to_h) at the end of call chain.

Whether one of the elements of an array or a hash is nil

Is there any method that tells that one of the elements of an array or a hash is nil?
For an array
array = [1, 2, 'a']
array.any?(&:nil?)
#=> false
For a hash, I guess you are talking about nil values.
hash = {:a => 1, :b => 2, :c => nil}
hash.value?(nil)
#=> true
You can use the any? method: http://ruby-doc.org/core-1.9.3/Enumerable.html#method-i-any-3F
For example:arr.any? { |x| x.nil? }
As oldergod and strmstn have pointed out you should use any, and in the condition inside block you can verify whether an element is a nil or its class is Hash
[1,2,nil].any? {|x| x.class == Hash or x.nil? } # => true
[1,2,{}].any? {|x| x.class == Hash or x.nil? } # => true

Search Ruby hash for value?

Trying to search through a hash for a value, no methods I have tried previously have worked.
def input
#search_term = STDIN.gets.chomp
end
def execute
#reader.searchKey(#search_term).each{|b| puts b}
end
def searchKey(search_term)
puts books_catalogue.has_value?(search_term)
end
hash = {foo: 'val', bar: 'other_val', bak: 'val'}
selected_hash = hash.select { |k,v| v == 'val' } # => {foo: 'val', bak: 'val'}
selected_hash.keys # => [:foo, :bak]
So the method looks like:
def search_key(value)
#hash.select { |k, v| v == value }.keys
end
Try this:
hash = {a: 1, b: 2, c: 2}
value_to_search_for = 2
hash.select {|_,value| value == value_to_search_for}
# output is {b: 2, c: 2}

Resources