Building a Hash in a Conditional Way

Building a hash in a conditional way

I prefer tap, as I think it provides a cleaner solution than the ones described here by not requiring any hacky deleting of elements and by clearly defining the scope in which the hash is being built.

It also means you don't need to declare an unnecessary local variable, which I always hate.

In case you haven't come across it before, tap is very simple - it's a method on Object that accepts a block and always returns the object it was called on. So to build up a hash conditionally you could do this:

Hash.new.tap do |my_hash|
my_hash[:x] = 1 if condition_1
my_hash[:y] = 2 if condition_2
...
end

There are many interesting uses for tap, this is just one.

build hash from conditional statements

merge makes this very concise:

def create_hash(opt ={})
{
a: 0.40,
b: 0.30,
c: 0.05
}.merge(opt)
end

create_hash :a => 0.30
# => {:a=>0.3, :b=>0.3, :c=>0.05}

We use merge in some of our in-house code to fine-tune configuration data. We have a global YAML file, then if we need to specify something different for a particular app or host, we have small YAML files containing just the hash data we need to modify. We merge the smaller hash with the global one and overwrite only the data that needs to change.

Looking at what you're doing:

hash = {:a => if opt[:a] ? a : 0.40; end},
:b => if opt[:b] ? b : 0.30; end,
:c => if opt[:c] ? c : 0.05; end}

There's a lot of confusion about how Ruby works. You'll immediately run into syntax errors:

opt = {}
hash = {:a => if opt[:a] ? a : 0.40; end},
:b => if opt[:b] ? b : 0.30; end,
:c => if opt[:c] ? c : 0.05; end}
# =>
# ~> -:3: syntax error, unexpected =>, expecting end-of-input
# ~> :b => if opt[:b] ? b : 0.30; end,
# ~> ^

That's because you can't:

  • use a trailing conditional inside a hash assignment.
  • end the first line with }, which terminates the hash construction.

Cleaning up the indentation and removing the if and end;...:

opt = {}
hash = {
:a => opt[:a] ? a : 0.40,
:b => opt[:b] ? b : 0.30,
:c => opt[:c] ? c : 0.05
}
# => {:a=>0.4, :b=>0.3, :c=>0.05}

That'd work, however it's still got code smell because it's difficult to see what's really happening because of the ternary ("?:") statements. Ternary statements aren't inherently evil, but they can be harder to read, so use them sparingly, and always be aware that they can affect the clarity of the code.

Conditional key/value in a ruby hash

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

Conditional inclusion of a key-value pair in a hash

def self.some_hash(some_key = nil)
{"foo" => "bar"}.merge(some_key ? {some_key => "yucky, long-winded syntax"} : {})
end

Or, if modifying the original hash,

def self.some_hash(some_key = nil)
{"foo" => "bar"}
.tap{|h| h.merge!(some_key => "yucky, long-winded syntax") if some_key}
end

Or, maybe you can do it in a way close to your original:

def self.some_hash(some_key = nil)
{"foo" => "bar"}
.tap{|h| h[some_key] = "yucky, long-winded syntax" if some_key}
end

Conditional map of Hash with join to concatenate a string

I believe this is an XY-question. I think you should do it this way:

aql_string = "FILTER " <<
conditions.map{|k, v| "user.#{k} == #{v.inspect}"}.join(" && ")

This will surround strings with double quotes instead of single quotes, but I believe that is not a problem.

Conditionally set value of key in hash

You just need some brackets around the values:

def parse(issue)
{
#...
asignee_handle: (issue['assignee']['login'] if issue['assignee']),
#...
}
end

The value of the :asignee_handle key will now either be issue['assignee']['login'] or it will be nil (or you will get an error if issue['assignee'] isn’t a hash).

Printing Values in a Hash using the conditional operator for and Ruby's .each

You're asking Ruby to do way too much at once here:

for q, e in my_stocks.to_i < 500

That really doesn't make any sense. for iterates over a container. to_i returns an integer. x.to_i < n returns a boolean. You can't iterate over a boolean. Plus, my_stocks is a Hash and it doesn't do to_i, so this whole line is not something Ruby can make sense of.

Instead think about the problem differently. The Ruby way is to break this down into steps. First, find all stocks over a particular value:

my_stocks.select do |_name, data|
data['price'] > 500
end.each do |name, data|
# ... print or whatever
end

Where this taps into Enumerable to help solve the problem in two stages. select to filter, each to iterate. In Ruby for isn't really used, it's really never the best tool for the job.

The real power of Enumerable is that you can chain one operation right into the next. end.each might look very odd to someone unfamiliar with Ruby, but that's really how Ruby handles complex operations with ease.

For example, to filter and sort by price (highest to lowest) is only a small modification:

my_stocks.select do |_name, data|
data['price'] > 500
end.sort_by do |_name, data|
-data['price']
end.each do |name, data|
# ... print or whatever
end

The -data['price'] part is an easy way of avoiding having to reverse the values later. Normally it sorts in increasing order, so inverting sorts in decreasing as the highest value becomes the lowest.



Related Topics



Leave a reply



Submit