I have a hash where the values are all arrays. I want to look up a key in this hash. If it exists I want to add a value to the array. If it does not exist (hash[key] returns nil) then I need to create the array and and add one value. Currently I have this:
hash[key].push elem unless hash[key].nul?
hash[key] ||= [elem]
This involves 3 lookups. I'm new to ruby so I'm sure there's a better way to do this. What is it?
My original plan was to make the default value for the hash [ ]. Then I can just use:
hash[key].push elem
Unfortunately if the key does not exist, that will only change the default value and not add a new key.
In this case you need to create a hash as below :
hash = Hash.new { |h,k| h[k] = [] }
The above is created to handle situations like your. Look new {|hash, key| block } → new_hash
hash = Hash.new { |h,k| h[k] = [] }
hash[:key1] << 1
hash[:key2] << 2
hash[:key1] << 3
hash # => {:key1=>[1, 3], :key2=>[2]}
You can try:
(hash[key] ||= []) << elem
However Arup's answer is much better.
You should create your hash with a default value.
hash = Hash.new { |h,k| h[k] = [] }
hash[key].push elem
Related
This question already has answers here:
Strange, unexpected behavior (disappearing/changing values) when using Hash default value, e.g. Hash.new([])
(4 answers)
Closed 1 year ago.
If...
variable = Hash.new(0)
...will default to new values being the integer zero without having to specify the associated key, why do I have to use a block and specify the associated key for the new values to default to an array, like so...
variable = Hash.new { |h, k| h[k] = [] }
I read ruby-doc.org but can't seem to find an answer. Perhaps its "under the hood" and I can't see/comprehend it.
For context, the question came up when I couldn't reconcile why the first method didn't work and the second method did:
def find_duplicates1(array)
indices = Hash.new([])
array.each_with_index { |ele, i| indices[ele] << i }
indices.select { |ele, indices| indices.length > 1 }
end
def find_duplicates2(array)
indices = Hash.new { |h, k| h[k] = [] }
array.each_with_index { |ele, i| indices[ele] << i }
indices.select { |ele, indices| indices.length > 1 }
end
Because indices = Hash.new([]) means that when calling it with an unknown key then the [] will be returned. But that empty default array will not be assigned to the former unknown key.
Here an example:
indices = Hash.new([])
indices[:foo] << :bar
indeces
#=> {}
But even worse, because we added a value to the default hash that hash is now not empty anymore and will return the changed default value for all other unknown keys too:
indices[:baz]
#=> [:bar]
Whereas indices = Hash.new { |h, k| h[k] = [] } means that the block will run for all unknown keys and within the block, a new empty array is initialized and that new array is actually assigned to the former unknown key.
indices = Hash.new { |h, k| h[k] = [] }
indices[:foo] << :bar
indices
#=> {:foo=>[:bar]}
indices[:bar]
#=> []
Btw you might be interested in the Enumerable#tally method. By using it your method can be simplified to:
def find_duplicates(array)
array.tally.select { |k, v| v > 1 }.keys
end
It's because the default (whatever object it is) is used as the default. That object will be presented for EVERY undefined instance. They're all pointing to the same object.
For immutable objects (like the integer 0) it doesn't matter because if you replace 0 with 1 for a given key, then the key is pointing to a new object (the integer 1).
But if it's an array object and you "mutate" (change) it like array << "added" then that object... now with added "added", is the default for all future new keys and is likely the object that all existing keys are pointing to. All keys point to the single array object that looks like: ["added"]
By using a block, you are defaulting a NEW array object to the key. If you change the array object by adding an element, the other keys' objects are unchanged (they're different objects).
Example as follows:
if house['windows'][floor_1]
house['windows'][floor_1] << north_side
else
house['windows'][floor_1] = [north_side]
end
Best way to check for existing key?
The fact that house['windows'] is an element in a hash already is a bit of a red herring, so I will use windows as a variable referencing a hash.
Set up a default value for the windows hash, so that any non-preexisting key is assigned an array value:
windows = Hash.new {|hash, key| hash[key] = [] }
Now you can append (<<) to new hash elements automatically.
windows['floor_1'] << 'north_side'
windows # => {"floor_1"=>["north_side"]}
For your specific case, replace windows with house['windows'].
EDIT
As pointed out in the comments, this behavior can be added to an already-instantiated hash:
windows.default_proc = proc {|hash, key| hash[key] = [] }
I would do something like:
house['windows'][floor_1] ||= []
house['windows'][floor_1] << north_side
Given your Hash, I imagine:
house = { windows: { floor_0: ['f0'] } }
You can check the existence of a key using Hash#has_key?
house[:windows].has_key? :floor_1 #=> false
So you can create it:
house[:windows].merge!({floor_1: []}) unless house[:windows].has_key? :floor_1
Better if you define a defalt value using for example Hash#default_proc=:
house[:windows].default_proc = proc { |h, k| h[k] = [] }
So you can
house[:windows][:floor_3] << 'f3'
house #=> {:windows=>{:floor_0=>["f0"], :floor_1=>[], :floor_3=>["f3"]}}
So basically my code is as follows
anagrams = Hash.new([])
self.downcase.scan(/\b[a-z]+/i).each do |key|
anagrams[key.downcase.chars.sort] = #push key into array
end
so basically the hash would look like this
anagrams = { "abcdef" => ["fdebca", "edfcba"], "jklm" => ["jkl"]}
Basically what I don't understand is how to push "key" (which is obviously a string) as the value to "eyk"
I've been searching for awhile including documentation and other stackflow questions and this was my best guess
anagrams[key.downcase.chars.sort].push(key)
Your guess:
anagrams[key.downcase.chars.sort].push(key)
is right. The problem is your hash's default value:
anagrams = Hash.new([])
A default value doesn't automatically create an entry in the hash when you reference it, it just returns the value. That means that you can do this:
h = Hash.new([])
h[:k].push(6)
without changing h at all. The h[:k] gives you the default value ([]) but it doesn't add :k as a key. Also note that the same default value is used every time you try to access a key that isn't in the hash so this:
h = Hash.new([])
a = h[:k].push(6)
b = h[:x].push(11)
will leave you with [6,11] in both a and b but nothing in h.
If you want to automatically add defaults when you access them, you'll need to use a default_proc, not a simple default:
anagrams = Hash.new { |h, k] h[k] = [ ] }
That will create the entries when you access a non-existent key and give each one a different empty array.
It's not entirely clear what your method is supposed to do, but I think the problem is that you don't have an array to push a value onto.
In Ruby you can pass a block to Hash.new that tells it what to do when you try to access a key that doesn't exist. This is a handy way to automatically initialize values as empty arrays. For example:
hsh = Hash.new {|hsh, key| hsh[key] = [] }
hsh[:foo] << "bar"
p hsh # => { :foo => [ "bar" ] }
In your method (which I assume you're adding to the String class), you would use it like this:
class String
def my_method
anagrams = Hash.new {|hsh, key| hsh[key] = [] }
downcase.scan(/\b[a-z]+/i).each_with_object(anagrams) do |key|
anagrams[key.downcase.chars.sort.join] << key
end
end
end
This question already has answers here:
How can I initialize an Array inside a Hash in Ruby [duplicate]
(3 answers)
Closed 8 years ago.
I am trying to create a hash that has a key and an array of values associated with the key.
So I was doing something like this
hash = Hash.new
and then later when I have a key I would do this
hash[key] << new_thing
but this fails if hash[key] is nil , so I added this statement before trying to append the new_thing to the array.
hash[key] = Array.new unless hash[key]
hash[key] << new_thing
I thought I could eliminate the added statement by changing the definition of hash to
hash = Hash.new Array.new
but this does not work because each entry shares the same array, what I really need is this
hash = Hash.new { |h,k| h[k] = Array.new }
I know I am probably splitting hairs but, I am just curious, what would be the "ruby" way to do this?
EDIT:
Ultimately what I ended up doing is using group_by. I realize it was not obvious from my question that I was trying to use this structure to group objects with the same property together in a hash. I am including this to encourage others that may find themselves using a similar structure for a similar purpose to check out group_by
["A", 1, "b", 3].group_by { |i| i.class }
=> {String=>["A", "b"], Fixnum=>[1, 3]}
The Ruby way to set an empty array as the default value for a Hash would be to use the default_proc version that is in your question:
hash = Hash.new { |h,k| h[k] = [ ] }
For example:
> hash = Hash.new { |h,k| h[k] = [ ] }
> hash[:pancakes] << 6
> hash
=> {:pancakes=>[6]}
Using standard Ruby features when writing Ruby seems pretty Rubyish to me.
I've seen people doing this more often:
(h[key] ||= []) << value
As that's the explicit way, and rubists like explicitness.
Your version is also common though.
By the way, you can set the defaults on existing hash:
h.default_proc = ->(h, k){ h[k] = [] }
or
h.default = 0
And I find that being a bit cleaner, but not as widely used.
you can set default value
numbers = Hash.new(0)
Is there any way simpler than
if hash.key?('a')
hash['a']['b'] = 'c'
else
hash['a'] = {}
hash['a']['b'] = 'c'
end
The easiest way is to construct your Hash with a block argument:
hash = Hash.new { |h, k| h[k] = { } }
hash['a']['b'] = 1
hash['a']['c'] = 1
hash['b']['c'] = 1
puts hash.inspect
# "{"a"=>{"b"=>1, "c"=>1}, "b"=>{"c"=>1}}"
This form for new creates a new empty Hash as the default value. You don't want this:
hash = Hash.new({ })
as that will use the exact same hash for all default entries.
Also, as Phrogz notes, you can make the auto-vivified hashes auto-vivify using default_proc:
hash = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }
UPDATE: I think I should clarify my warning against Hash.new({ }). When you say this:
h = Hash.new({ })
That's pretty much like saying this:
h = Hash.new
h.default = { }
And then, when you access h to assign something as h[:k][:m] = y, it behaves as though you did this:
if(h.has_key?(:k))
h[:k][:m] = y
else
h.default[:m] = y
end
And then, if you h[:k2][:n] = z, you'll end up assigning h.default[:n] = z. Note that h still says that h.has_key?(:k) is false.
However, when you say this:
h = Hash.new(0)
Everything will work out okay because you will never modified h[k] in place here, you'll only read a value from h (which will use the default if necessary) or assign a new value to h.
a simple one, but hash should be a valid hash object
(hash["a"] ||= {})['b'] = "c"
If you create hash as the following, with default value of a new (identically default-valued) hash: (thanks to Phrogz for the correction; I had the syntax wrong)
hash = Hash.new{ |h,k| h[k] = Hash.new(&h.default_proc) }
Then you can do
hash["a"]["b"] = "c"
without any additional code.
The question here:
Is auto-initialization of multi-dimensional hash array possible in Ruby, as it is in PHP?
provides a very useful AutoHash implementation that does this.
class NilClass
def [](other)
nil
end
end
Once you defined that, everything will work automatically. But be aware that from now on nil would behave as an empty hash when used as a hash.