I am trying to understand this, so let me know how I do. :) I know
this is probably a breeze for you veterans, but I need to put this in
words. heh
I have a C++/Perl background, so I do understand "associative arrays",
but understanding Ruby hash objects, and the methods and operations that
can be used with them is pretty new to me.
In the "Ruby Koans" file, "about_hashes.rb", I completed this method.
It was just a little tough to understand, initially, but I think I got
it. Here's the test method with helpful line numbers:
def test_default_value_is_the_same_object
1. hash = Hash.new([])
2. hash[:one] << "uno"
3. hash[:two] << "dos"
4. assert_equal ["uno", "dos"], hash[:one]
5. assert_equal ["uno", "dos"], hash[:two]
6. assert_equal ["uno", "dos"], hash[:three]
7. assert_equal true, hash[:one].object_id == hash[:two].object_id
end
Here is my breakdown:
1. a new hash is created, utilizing an empty array as the default value
for any key created without a value, and assigned to "hash". This
wasn't too bad, I just had to look up: Hash.new(<place a default key
value here>)
2. using indexing, the string, "uno", is appended to the default key
value for the key, :one, which was initially an empty array and is now
the value ["uno"]. Since the "<<" was used instead of the assignment
operator "=", it is an append, rather than an assign. For example, if
the statement was "hash[:one] = "uno", then that replaces the default
array and the value is now a String
3. same as 2., but what I found interesting was how a new key was used,
and the append still modified the general default value for the entire
hash! So, now the key value default is now: ["uno", "dos"]
4 - 6 test cases to prove that a default key value was created, with
lines 2 and 3 appending to it, and because these were not assignment
expressions, the keys tested (:one, :two, :three) are using the modified
default value.
7. Final proof that is the same default value, or same Array object_id,
being used for the two utilized keys.
So, when you create a Hash object, you can provide a default value. In
this case, the default value is an Array object. Because of this being
an Array object, the "<<" method can be used to modify the default
value. Using indexing on the Hash, this can be tested by querying the
hash:
hash = Hash.new([])
=> {}
hash[:foo]
=> []
hash[:foo] << "hello"
=> ["hello"]
hash[:foo]
=> ["hello"]
hash.inspect
=> {}
The last line "hash.inspect" is what I also find interesting, because if
there are default values, why is it still empty? I then assign a value,
overriding the default value:
hash[:foo] = "bar"
=> "bar"
hash.inspect
=> "{:foo=>\"bar\"}"
Now, inspect no longer shows an empty hash. Hmmm...
Ruby hashes are an interesting topic and I look forward to your veteran
insight on this topic.
Thanks!
on 2012-12-28 06:27
on 2012-12-28 06:47
Well seems that if you don't assing an explicit value to a hash key then
nothing happens.
When you assing the default value of the Hash in it initialize method(
Hash.new("default value") you have to think of it as an object, you can
change it, of course, it is an object, and it's a value that's returned
when you call for a key that doesn't has an expecific value.
hash = Hash.new("go fish")
hash[:a_key_that_does_not_have_assigned_an_explicit_value].capitalize!
puts hash[:another_one]
#=> "Go fish"
hash[:third_with_no_explicit_value].upcase!
puts hash[:another_more]
#=> "GO FISH"
hash[:another_more] = "something"
puts hash[:another_more]
#=> "something"
Well don't know it help, cheers!
on 2012-12-28 09:15
Derrick B. wrote in post #1090445: > > hash = Hash.new([]) > => {} > hash[:foo] > => [] > hash[:foo] << "hello" > => ["hello"] > hash[:foo] > => ["hello"] > hash.inspect > => {} > > The last line "hash.inspect" is what I also find interesting, because if > there are default values, why is it still empty? I then assign a value, > overriding the default value: > 1) You never write Hash.new([]) .. (or with any other mutable type as the argument) because the same array is the default for every key, and that is never useful. 2) When you write: hash[:non_existent_key] ... you get a reference to the single default array. Unless you *assign* that value to the key, then that key will still not have a value. So you need to write: hash[:non_existent_key] <<= "hello" > hash[:foo] = "bar" > => "bar" > hash.inspect > => "{:foo=>\"bar\"}" > > Now, inspect no longer shows an empty hash. Hmmm... > Yes. In that example you assigned something to the key. > Ruby hashes are an interesting topic and I look forward to your veteran > insight on this topic. > This is what you want: hash = Hash.new {|hash, key| hash[key] = []} hash[:one] << 'hello' hash[:two] << 'goodbye' p hash --output:-- {:one=>["hello"], :two=>["goodbye"]} In this case, the block executes *every time* you access a non-existent key, and the block creates a new array and assigns it to the key, *and* the return value is a reference to the array that was assigned to the key. As a result, all you need to do is append to the array. It can be useful to use the first type of hash creator with non-mutable types as an argument: hash = Hash.new 0 hash[:one] += 1 hash[:two] += 1 p hash --output:-- {:one=>1, :two=>1}
on 2012-12-28 09:32
hash = Hash.new do |hash, key| arr = [] p arr.object_id hash[key] = arr end p hash[:one].object_id p hash[:two].object_id p hash --output:-- 2156082500 2156082500 2156082420 2156082420
on 2012-12-28 18:17
7stud -- wrote in post #1090450: > > 1) You never write > > Hash.new([]) > > .. (or with any other mutable type as the argument) because the same > array is the default for every key, and that is never useful. > That makes sense, and I got that same uselessness feeling when I saw the assertions made against different indexes. > > This is what you want: > > hash = Hash.new {|hash, key| hash[key] = []} > > hash[:one] << 'hello' > hash[:two] << 'goodbye' > > p hash > > --output:-- > {:one=>["hello"], :two=>["goodbye"]} > That was actually the very next test block in that "about_hashes.rb" Koans file: def test_default_value_with_block hash = Hash.new {|hash, key| hash[key] = [] } hash[:one] << "uno" hash[:two] << "dos" assert_equal ["uno"], hash[:one] assert_equal ["dos"], hash[:two] assert_equal [], hash[:three] end > > In this case, the block executes *every time* you access a > non-existent key, and the block creates a new array > and assigns it to the key, *and* the return value is a > reference to the array that was assigned to the key. > As a result, all you need to do is append to the array. > This is one of those "I get it, but then I do not get it" paradoxes. I understand whatever is returned from the block is assigned to "hash", right? So, inside the block, a hash key is generated with an array as its value: hash[key] = [] But, since an append method was used, the value being appended, in this case "uno" or "dos", now becomes the value for whatever key index, :one or :two, is provided: hash[:one] = ["uno"] Since the block is executed every time, a new array object is created, so each key has different array object id. So this: hash = hash[:one] = ["uno"] Did this: Unique array id ([]), appended with a string ("uno"), assigned to hash key (:one), assigned to variable (hash). > It can be useful to use the first type of hash creator with non-mutable > types as an argument: > > hash = Hash.new 0 > > hash[:one] += 1 > hash[:two] += 1 > > p hash > > --output:-- > {:one=>1, :two=>1} Or as an accumulator: >> hash = Hash.new 0 => {} >> hash[:one] => 0 >> hash[:one] += 1 => 1 >> hash[:one] => 1 >> hash[:one] += 1 => 2 >> hash[:one] += 1 => 3 >> hash[:one] += 1 => 4 ...which I currently have no idea how that could be useful, but it is still interesting... heh Looks like I am not the only one that found that Ruby Koans lesson a bit confusing: http://stackoverflow.com/questions/9537714/rubykoa... http://stackoverflow.com/questions/9343680/how-doe... Thanks, that helped clear some things up.
on 2012-12-28 21:14
> ...which I currently have no idea how that could be useful, but it is > still interesting... heh string = "Hello world, hello mars, goodbye world." word_count_for = Hash.new(0) string.scan(/\b \w+ \b/x) do |word| word = word.downcase word_count_for[word] += 1 end p word_count_for --output:-- {"hello"=>2, "world"=>2, "mars"=>1, "goodbye"=>1}
on 2012-12-28 21:28
> This is one of those "I get it, but then I do not get it" paradoxes. You said you know C++ and perl. They both employ the concept of "references". > I understand whatever is returned from the block is assigned to "hash", > right? No. Inside the block, the code assigns an array reference to the key. Then the block returns a reference to the same array. Two references to the same array. Either one can be used to change the array. Here is a simple example of that: ref1 = {} ref2 = ref1 ref1[:a] = 10 ref2[:b] = 20 p ref1 --output:-- {:a=>10, :b=>20} In ruby, the following is an array constructor: [] Every time that array constructor executes it creates a new array: puts [].object_id puts [].object_id puts [].object_id --output:-- 2156185940 2156185880 2156185840 When you write: hash = Hash.new [] that array constructor only executes once. But when you write: hash = Hash.new { |hash, key| hash[key] = [] } ...ruby stores the block somewhere, and then every time you try to access a non-existent key, ruby executes the block.
on 2012-12-28 23:51
7stud -- wrote in post #1090511: > > ...ruby stores the block somewhere, and then every time you try to > access a non-existent key, ruby executes the block. > I don't know if you know it or not but some of ruby's built in methods have strange names. For instance, when you write: hash[key] << 'hello' …the left side is syntactic sugar for this method call: hash.[](key) In that line, the method's name is "[]". And when the argument to the method is a non-existent key, the [] method returns an array reference. And of course, method calls in your code are replaced by the method's return value, so if you write: hash[:non_existent] << 'hello' which is equivalent to: hash.[](:non-existent) << 'hello' …then that becomes: array_ref << 'hello'
on 2012-12-29 00:08
7stud -- wrote in post #1090511: > > > No. Inside the block, the code assigns an array reference to the key. > Then the block returns a reference to the same array. Two references > to the same array. Either one can be used to change the array. Here is > a simple example of that: > > ref1 = {} > ref2 = ref1 > > ref1[:a] = 10 > ref2[:b] = 20 > > p ref1 > > --output:-- > > {:a=>10, :b=>20} > Whoops. An example of two references to the same array would look like this: ref1 = [] ref2 = ref1 ref1 << 10 ref2 << 20 p ref1 --output:-- [10, 20] my_hash = {} array_ref = my_hash[:one] = [] array_ref << 'hello' << 'world' --output:-- {:one=>["hello", "world"]} p my_hash
on 2012-12-29 00:49
7stud -- wrote in post #1090510: >> ...which I currently have no idea how that could be useful, but it is >> still interesting... heh > > string = "Hello world, hello mars, goodbye world." > word_count_for = Hash.new(0) > > string.scan(/\b \w+ \b/x) do |word| > word = word.downcase > word_count_for[word] += 1 > end > > p word_count_for > > --output:-- > {"hello"=>2, "world"=>2, "mars"=>1, "goodbye"=>1} Hey, I said "currently have no idea"! :) Very cool example.
on 2012-12-29 01:38
7stud -- wrote in post #1090511: >> This is one of those "I get it, but then I do not get it" paradoxes. > > You said you know C++ and perl. They both employ the concept of > "references". > It wasn't the reference part that was confusing, just the contents of the block and what it was doing. { |hash, key| hash[key] = [] } >> I understand whatever is returned from the block is assigned to "hash", >> right? > > No. Inside the block, the code assigns an array reference to the key. > Then the block returns a reference to the same array. Two references > to the same array. I had that mostly correct in my head, but two references to the same array??? After creating the new Hash object, the hash variable is just the Hash reference, but the indexed Hash reference ( like hash[:one] ) points to the array reference, right? >> hash = Hash.new { |hash, key| hash[key] = [] } => {} >> hash => {} >> hash[:one] => [] >> hash.object_id => 269913890 >> hash[:one].object_id => 269881860 Two different id's. Or did I do that wrong? Two references to the same array in that assignment expression above doesn't sound right. Your other example: ref1 = [] ref2 = ref1 ref1 << 10 ref2 << 20 ...makes sense of two references to the same array. > > In ruby, the following is an array constructor: > > [] > That I do understand, because Perl is similar: my $a = []; Ok, maybe you are about to tell me otherwise here, too, but it sure looks similar to me! heh perl -le '$a = []; push @$a, "hello"; print @$a' --output-- hello
on 2012-12-29 02:01
7stud -- wrote in post #1090519: > 7stud -- wrote in post #1090511: >> >> ...ruby stores the block somewhere, and then every time you try to >> access a non-existent key, ruby executes the block. >> > > I don't know if you know it or not but some of ruby's built in methods > have strange names. For instance, when you write: > > hash[key] << 'hello' > > …the left side is syntactic sugar for this method call: > > hash.[](key) > Yep, read about it in section 4.4 of "The Ruby Programming Language", but don't quiz me on it yet! :)
on 2012-12-29 03:08
> Derrick B. wrote in post #1090528: > I had that mostly correct in my head, but two references to the same > array??? After creating the new Hash object, the hash variable is just > the Hash reference, but the indexed Hash reference ( like hash[:one] ) > points to the array reference, right? > >>> hash = Hash.new { |hash, key| hash[key] = [] } > => {} >>> hash > => {} >>> hash[:one] > => [] >>> hash.object_id > => 269913890 >>> hash[:one].object_id > => 269881860 > > Two different id's. Or did I do that wrong? Two references to the same > array in that assignment expression above doesn't sound right. Your > other example: 1) The block does not create the hash. If you look at the block: Hash.new { |hash, key| hash[key] = [] } ...you can see that the block takes a hash as an argument. The hash gets created elsewhere. 2) You cannot look at a block and know what a method returns. A block is like an argument to a method. In essence, you are passing a function to the method. Here is an example: def do_stuff(x, y, &block) #&block is the syntax to capture the block #in a variable if block_given? block_return_val = block.call puts "Block returns: %s" % block_return_val end 'hello' #This is the return val of do_stuff end do_stuff_return_val = do_stuff(10, 20) {'goodbye'} puts "do_stuff returns: %s" % do_stuff_return_val --output:-- Block returns: goodbye do_stuff returns: hello However, it is pretty obvious if you are a programmer that a method named new is going to return a new object. So when you write: hash = Hash.new { |hash, key| hash[key] = [] } ... hash is going to be a reference to a new Hash. Therefore, you can deduce that the new() method of the Hash class returns a reference to a new Hash. Note that the block returns whatever a method named []= in the Hash class returns. 3) Here is a simple example of how things might work in the Hash class: class Dog def initialize #This method is similar to a constructor @hash = {} #"@ variables" are instance variables @func = lambda { |hash, key| hash[key] = [] } #lambda creates a function end def hash #getter method @hash end def []=(key, val) h[key] = val #The return val of the method is the right hand side end def [](key) if hash.keys.include? key h[key] else @func.call hash, key end end def to_s @hash.inspect end end d = Dog.new d[:non] << 'hello' p d --output:-- {:non=>["hello"]} When the key doesn't exist, the [] method returns whatever func() returns, which is whatever the []= method returns, which is val, which is the newly created array.
on 2012-12-29 03:53
7stud -- wrote in post #1090536: > > def to_s > @hash.inspect > end > > end > To be consistent, instead of using @hash in the to_s() method, the code should use the getter: def to_s hash.inspect end
on 2012-12-29 04:26
7stud -- wrote in post #1090536: > > 1) The block does not create the hash. If you look at the block: > > Hash.new { |hash, key| hash[key] = [] } > Agreed. Since we went from a simple: hash = Hash.new # no argument to new to hash = Hash.new { |hash, key| hash[key] = [] } # block as argument to new I just wanted to understand how the block supplied the argument to new. Done! Thanks for the examples,
on 2012-12-29 04:41
Derrick B. wrote in post #1090541: > > I just wanted to understand how the block supplied the argument to new. > To be clear, the block does not execute when new() is called. Instead, new() stores the block somewhere, and only when a non-existent key is accessed does the block execute. > Thanks for the examples, Sure. Good luck with ruby.
on 2012-12-29 05:52
Great discussion here! I now understand how to use that hash block in initializing a hash.
Please log in before posting. Registration is free and takes only a minute.
Existing account
(Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
Log in with Google account | Log in with Yahoo account
No account? Register here.