10

I have a Hash in Ruby:

hash = Hash.new 

It has some key value pairs in it, say:

hash[1] = "One" hash[2] = "Two" 

If the hash contains a key 2, then I want to add "Bananas" to its value. If the hash doesn't have a key 2, I want to create a new key value pair 2=>"Bananas".

I know I can do this by first checkng whether the hash has the key 2 by using has_key? and then act accordingly. But this requires an if statement and more than one line.

So is there a simple, elegant one-liner for achieving this?

3
  • 2
    And what code have you written toward solving this? Commented Mar 24, 2014 at 15:52
  • 5
    Something to understand about coding: Writing code that only takes one line isn't the goal. The goal is to write code that is "elegant", which mean understandable and powerful, not necessarily "terse". A simple conditional and modification in two or three lines is perfectly good if it's readable and accomplishes what you need. You'll find the programming world is full of three-line versions of what you're asking, simply because it is readable and just as fast. Commented Mar 24, 2014 at 16:06
  • Ruby allows semicolons instead of line breaks. You can write everything in one line, just by replacing line breaks with semicolons. Problem solved. Commented Mar 24, 2014 at 20:34

5 Answers 5

11

This works:

hash[2] = (hash[2] || '') + 'Bananas' 

If you want all keys to behave this way, you could use the "default" feature of Ruby's Hash:

hash = {} hash.default_proc = proc { '' } hash[2] += 'Bananas' 
Sign up to request clarification or add additional context in comments.

5 Comments

Thank you or your answer, I'll accept it as soon as I can. Though I don't really understand: What does the (hash[2]||'') part mean
If hash[2] returns a value it's "truthy", which satisfies the || (boolean OR). If hash[2] does not have a value, nil is returned, which is "falsey" causing || to fire off, and return "". Which is the exact reason I commented above that a three-line version is more readable. Using Boolean logic this way is an advanced technique we use at the cost of readability.
The default= method with a mutable object is a bug waiting to happen:hash[3]<<"pinapple"; p h[4] #=>"pineapple"
@sttenslag, you're right. I changed the answer to use default_proc instead so you generate a new string each time. Another interesting option would be hash.default = ''.freeze.
It might be worth adding to the answer why you are using default_proc= instead of default=, for those who don't read the comments.
5

You could set the default value of the hash to an empty string, then make use of the << operator to concat whatever new values are passed:

h = Hash.new("") #=> {} h[2] << "Bananas" #=> "Bananas" h #=> {2=>"Bananas"} h[2] << "Bananas" #=> "BananasBananas" 

Per @rodrigo.garcia's comment, another side effect of this approach is that Hash.new() sets the default return value for the hash (which may or may not be what you want). In the example above, that default value is an empty string, but it doesn't have to be:

h2 = Hash.new(2) #=> {} h2[5] #=> 2 

4 Comments

a += b, being equivalent to a = a + b is less efficient than << or concat because it creates a copy of the string. The others will "really" append to the existing string. Other than that I think your solution is the best :)
Ah yes, that's a great point. Thanks for the comment, I've updated my answer.
Using Hash.new(x) will always return the same instance x as the default value. Since here you are modifying "" by concatenating "Bananas" to it twice, now every unset key will return "BananasBananas" (try doing h[4] for example). This might be the desired behavior in some cases, but you should explain it in your answer.
@rodrigo.garcia - added a note.
4
(hash[2] ||= "").concat("Bananas") 

9 Comments

<< is just an alias for concat
@p11y They do have differences also.. But that's not the point here.. :)
@ArupRakshit can't imagine that there would be a difference, since the both call rb_str_concat internally. It's really just an alias
But what you show is an Array, not a String. On the Array class << is an alias for push. Confusing, I admit. Here, we are operating on a String.
@ArupRakshit Well then I must disagree again :) On the String class << is an alias for concat. On the Array class, << is an alias for push. Just because the methods have the same name doesn't mean they have anything to do with each other.
|
1

Technically, both your roads lead to the same place. So Hash[2] = "bananas" produces the same result as first checking the hash for key 2. However, if you actually need the process of checking the hash for some reason a way to do that is use the .has_key? method, and a basic if conditional.

Suppose there is a hash,

`Hash = { 1 => "One", 2 => "Two" }` 

setup a block of code based on the truth-value of a key search,

if hash.has_key?(2) == true hash[2] = "bananas" else hash[2] = "bananas" end

or more simply,

hash.has_key?(2) == true ? hash[2] = "bananas" : hash[2] = "bananas"

Comments

0

You can initialise the hash with a block and then directly concatenate:

hash = Hash.new {|hash, key| hash[key] = ""} hash[1] << "One" hash[2] << "Two" hash[2] << "Bananas" {1=>"One", 2=>"TwoBananas"} 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.