2

I am trying to build a hash from conditional statements

def create_hash(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}
end

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

i have tried using tap, but it seems like it will only give back one value if a condition is met instead of returning an else value - Building a hash in a conditional way. is there a good way to create conditional hashes like this?

Community
  • 1
  • 1
heinztomato
  • 795
  • 1
  • 7
  • 12

4 Answers4

4

I would use the "or equals" operator: ||=

def create_hash(opt = {})
  hash = opt.dup

  hash[:a] ||= 0.40
  hash[:b] ||= 0.30
  hash[:c] ||= 0.05
  hash
end
Community
  • 1
  • 1
August
  • 12,410
  • 3
  • 35
  • 51
  • 1
    If the OP is equating a value of `nil` with `opt.key? => false`, `hash[:a] = 0.40 unless hash.key?(:a)` would be more robust, as `hash[:a] ||= 0.40` doesn't work when `opt => { a: nil }`. – Cary Swoveland Nov 30 '14 at 22:49
4

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.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
2

Try this:

def create_hash(opt = {})
  hash = Hash.new

  hash[:a] = opt[:a] || 0.4
  hash[:b] = opt[:b] || 0.3
  hash[:c] = opt[:c] || 0.05

  hash
end
dax
  • 10,779
  • 8
  • 51
  • 86
  • edited answer, i didn't catch that you were trying to set default values when i first read your question – dax Nov 30 '14 at 21:07
  • i'll take an upvote if you're so kind, but actually i think august's answer is nicer, s/he should get the tick :) – dax Nov 30 '14 at 21:10
0

I prefer @theTinMan's use of merge but here's a way that may acquaint some readers with an unfamiliar method.

def create_hash(opt = {})
  hash = { a: 0.40, b: 0.30, c: 0.05 }
  opt.default_proc = proc { |h, k| h[k] = hash[k] } 
  opt.values_at *hash.keys
  opt
end

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

opt.values_at *hash.keys is one of many ways to create the keys in hash but not in opt.

Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100