Strange behavior in ruby 1.8.7

16 views
Skip to first unread message

david amick

unread,
Apr 30, 2013, 7:10:20 PM4/30/13
to pdxruby-...@googlegroups.com
Wrote a gem in ruby 1.9.3, then realized it's application deserves being compatible with 1.8.7. After switching, my tests caught an array getting returned in a reversed order. It doesn't actually matter in my case, since it's just two command line flags being joined, and they can be in either order.

I'm not asking for help with my code, rather I'm wanting to understand how it's possible that ruby would do the following:

This is the line of code that returns in one order in 1.9.3, and the reverse order in 1.8.7 in my tests:
{ :y => $options[:assume_yes], :s => $options[:dry_run] }.collect { |k, v| if v ; "-#{k}" ; end }.join(' ').strip

Had an irb session open for a while trying various things. I then tried to repeat the reversed array problem in irb to understand where it's happening:
{ :y => true, :s => true }.collect { |k, v| if v ; "-#{k}" ; end }.join(' ').strip
Could NOT repeat, it kept returning in the same order as 1.9.3. OK, sanity check, restart irb, run the same line again:
{ :y => true, :s => true }.collect { |k, v| if v ; "-#{k}" ; end }.join(' ').strip
Boom! It returns in reversed order. So I went back through my previous irb session restarting irb each time and individually trying each command I had previously run in irb to see if I could repeat the weirdness. Well I found it, and it literally relates in no way to the line in question:

start irb,
{ :y => true, :s => true }.collect { |k, v| if v ; "-#{k}" ; end }.join(' ').strip => '-s -y'
exit irb.

start irb,
a = { :a => nil }
{ :y => true, :s => true }.collect { |k, v| if v ; "-#{k}" ; end }.join(' ').strip => '-s -y'

exit irb.

start irb,
b = { :a => nil }
{ :y => true, :s => true }.collect { |k, v| if v ; "-#{k}" ; end }.join(' ').strip => '-y -s'

exit irb.

ALSO:
start irb,
a = { :b => nil }
{ :y => true, :s => true }.collect { |k, v| if v ; "-#{k}" ; end }.join(' ').strip => '-s -y'

exit irb.

start irb,
a = { :d => nil }
{ :y => true, :s => true }.collect { |k, v| if v ; "-#{k}" ; end }.join(' ').strip => '-y -s'

exit irb.

You can go on through the alphabet and various letters will cause the reverse action or not, reliably. Since irb has to be restarted each time, after manually going through most of the alphabet, I wrote a simple shell script to start irb, run the ruby code, and exit irb - for each letter of the alphabet. Whether using the script, or starting up irb by hand each time, these are the results you'll get:

a = { :a => nil } ; (code) => '-s -y'    a = { :a => nil } ; (code) => '-s -y'
a = { :b => nil } ; (code) => '-s -y'    b = { :a => nil } ; (code) => '-s -y'
a = { :c => nil } ; (code) => '-s -y'    c = { :a => nil } ; (code) => '-s -y'
a = { :d => nil } ; (code) => '-y -s'    d = { :a => nil } ; (code) => '-y -s'
a = { :e => nil } ; (code) => '-s -y'    e = { :a => nil } ; (code) => '-s -y'
a = { :f => nil } ; (code) => '-s -y'    f = { :a => nil } ; (code) => '-s -y'
a = { :g => nil } ; (code) => '-y -s'    g = { :a => nil } ; (code) => '-y -s'
a = { :h => nil } ; (code) => '-y -s'    h = { :a => nil } ; (code) => '-y -s'
a = { :i => nil } ; (code) => '-s -y'    i = { :a => nil } ; (code) => '-s -y'
a = { :j => nil } ; (code) => '-y -s'    j = { :a => nil } ; (code) => '-y -s'
a = { :k => nil } ; (code) => '-s -y'    k = { :a => nil } ; (code) => '-s -y'
a = { :l => nil } ; (code) => '-s -y'    l = { :a => nil } ; (code) => '-s -y'
a = { :m => nil } ; (code) => '-s -y'    m = { :a => nil } ; (code) => '-s -y'
a = { :n => nil } ; (code) => '-s -y'    n = { :a => nil } ; (code) => '-s -y'
a = { :o => nil } ; (code) => '-y -s'    o = { :a => nil } ; (code) => '-y -s'
a = { :p => nil } ; (code) => '-s -y'    p = { :a => nil } ; (code) => '-s -y'
a = { :q => nil } ; (code) => '-y -s'    q = { :a => nil } ; (code) => '-y -s'
a = { :r => nil } ; (code) => '-y -s'    r = { :a => nil } ; (code) => '-y -s'
a = { :s => nil } ; (code) => '-s -y'    s = { :a => nil } ; (code) => '-s -y'
a = { :t => nil } ; (code) => '-s -y'    t = { :a => nil } ; (code) => '-s -y'
a = { :u => nil } ; (code) => '-y -s'    u = { :a => nil } ; (code) => '-y -s'
a = { :v => nil } ; (code) => '-s -y'    v = { :a => nil } ; (code) => '-s -y'
a = { :w => nil } ; (code) => '-y -s'    w = { :a => nil } ; (code) => '-y -s'
a = { :x => nil } ; (code) => '-y -s'    x = { :a => nil } ; (code) => '-y -s'
a = { :y => nil } ; (code) => '-s -y'    y = { :a => nil } ; (code) => '-s -y'
a = { :z => nil } ; (code) => '-y -s'    z = { :a => nil } ; (code) => '-y -s'


As you can see, each side is has the same pattern. I also noticed:
It does not matter what you set the value of the hash to.
It does not matter if you use a string for a key in the has instead of symbol.
It has nothing to do with join, strip, or collect (any enumeration action will do.)
It does not matter if you have letters following the string or symbol key name. (Like a = { :zash => nil }.)

I want to understand the scope of the language in which I'm working. How is it possible in any ruby version that setting certain arbitrary hash variables before enumerating over an arbitrary array could reverse the outcome?

Thanks!

Sam Livingston-Gray

unread,
Apr 30, 2013, 8:30:53 PM4/30/13
to pdxruby-...@googlegroups.com
Short version: in 1.8, the order of items in a Hash is deterministic but nonsensical (hence ActiveSupport's OrderedHash class). In 1.9, Hashes are sorted by, if I remember correctly, order of insertion. This is a "data structures" question; I'd look up a link, but have to go now. Anyone? :)

--
(Sent from phone. Please excuse: brevity, top posting, hilarious autocorrections.)
--
You received this message because you are subscribed to the Google Groups "pdxruby-beginners" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pdxruby-beginn...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Matthew Boeh

unread,
Apr 30, 2013, 9:04:23 PM4/30/13
to pdxruby-...@googlegroups.com
To follow up on what Sam said, in Ruby 1.8.7 the hash value of symbols is based on object ID, which is deterministic based on the number of objects that have been created:

mboeh@sterling:~$ for i in $(seq 1 10); do ruby -e 'puts :a.object_id'; done
218428
218428
218428
218428
218428
mboeh@sterling:~$ for i in $(seq 1 5); do ruby -e ':b.object_id; puts :a.object_id'; done
218588
218588
218588
218588
218588

And in Ruby 1.8, Object#hash defaults to Object#object_id -- including for Symbol.

That's why you're seeing what you're seeing -- the order of a hash's elements depends in on how many objects have been created before the hash elements are created.

As of at least 1.9.3, Ruby has introduced a random scattering element to object hashes, in part to improve hash performance: 

mboeh@sterling:~$ for i in $(seq 1 5); do ruby -e 'puts :a.hash'; done
2540368129227438309
-4435615436060730524
-3112607153049541912
-2231447543609186015
-4090378446410991499
mboeh@sterling:~$ ruby -v
ruby 2.0.0p0 (2013-02-24 revision 39474) [x86_64-linux]

Reply all
Reply to author
Forward
0 new messages