Ordered hash hack for < ruby 1.9?

B

Ben Johnson

I am having an issue testing my code because hashes don't have a
consistent order when you iterate over them. For example, one of the
methods that I am trying to test iterates over a hash and creates a
string. That string is in a different order every time, and my tests
keep failing.

What would be perfect is if I could modify how the Hash class works so
it preserves the insert order, but only when I am testing. So I could
include this file in my tests.

Is there anything out there that does this? Thanks for your help.
 
L

Luis Parravicini

I am having an issue testing my code because hashes don't have a
consistent order when you iterate over them. For example, one of the
methods that I am trying to test iterates over a hash and creates a
string. That string is in a different order every time, and my tests
keep failing.

What would be perfect is if I could modify how the Hash class works so
it preserves the insert order, but only when I am testing. So I could
include this file in my tests.

Is there anything out there that does this? Thanks for your help.

Hash's == method compares if two hashes are equal (
http://www.ruby-doc.org/core/classes/Hash.html#M002875 )


Bye
 
P

Patrick Doyle

[Note: parts of this message were removed to make it a legal post.]

Why not iterate over myhash.keys.sort instead of just myhash.keys?

--wpd
 
B

Ben Johnson

Luis said:
Hash's == method compares if two hashes are equal (
http://www.ruby-doc.org/core/classes/Hash.html#M002875 )


Bye

I realize that. My class is creating a hash of complex objects. For me
to create the hash by hand would take a long time and be a huge pain in
the ass. Plus this method does other things with the hash, and
ultimately returns a string. I want to test this method and it's
impossible since ruby iterates over a hash in a random order.
 
A

ara.t.howard

What would be perfect is if I could modify how the Hash class works so
it preserves the insert order, but only when I am testing. So I could
include this file in my tests.

Is there anything out there that does this? Thanks for your help.
--


gem install orderedhash


a @ http://codeforpeople.com/
 
B

Ben Johnson

Patrick said:
Why not iterate over myhash.keys.sort instead of just myhash.keys?

--wpd

Because for performance it's bad. I don't care if performance is bad in
my tests. Which is why it would be nice to alter how hashes work ONLY in
my test environment. But sorting by keys isn't smart either.
NoMethodError: undefined method `<=>' for :a:Symbol
from (irb):16:in `sort'
from (irb):16
from :0
 
L

Lex Williams

Ben said:
Because for performance it's bad. I don't care if performance is bad in
my tests. Which is why it would be nice to alter how hashes work ONLY in
my test environment. But sorting by keys isn't smart either.

NoMethodError: undefined method `<=>' for :a:Symbol
from (irb):16:in `sort'
from (irb):16
from :0

Here's a hack for altering the original Hash class :

class Hash
alias :eek:ld_equals :[]=
attr_reader :eek:rdered_values

def []=(key,value)
@ordered_values ||= []
@ordered_values << key
old_equals(key,value)
end


end

hsh = {}
hsh["a"]="b"
hsh["b"]="c"
hsh.ordered_values.each do |key|
puts key
end

###
outputs a,b
 
L

Lex Williams

That should have been called ordered_keys instead of ordered_values ...
sorry , speed coding does this to me. But,in rest,the code works .
 
B

Ben Johnson

Lex said:
That should have been called ordered_keys instead of ordered_values ...
sorry , speed coding does this to me. But,in rest,the code works .

Awesome, this is exactly what I need, the only problem is that it
doesn't work when you do:

hsh = {"a" => "b"}

I'm trying to get this to work but having no luck.
 
B

Ben Johnson

Lex said:
That should have been called ordered_keys instead of ordered_values ...
sorry , speed coding does this to me. But,in rest,the code works .

This seemed to do the trick for me:

class Hash
def each(&block)
sorted_keys = keys.sort { |a, b| a.to_s <=> b.to_s }
sorted_keys.each do |key|
yield key, self[key]
end
self
end
end

Thanks for your help.
 
T

Trans

I realize that. My class is creating a hash of complex objects. For me
to create the hash by hand would take a long time and be a huge pain in
the ass. Plus this method does other things with the hash, and
ultimately returns a string. I want to test this method and it's
impossible since ruby iterates over a hash in a random order.

Since a hash is an unordered collection, one should not test it
according to any order. Instead test to see if a key is present and it
has certain values.

That you want it ordered should raise a red flag for you.

T.
 
B

Ben Johnson

Lex said:
this sorts them alphabetically , not by insertion order.

I didn't need them by insertion order, just to be consistent. I
apologize if I made that unclear.

Also, I am not testing a hash, I am testing a method that returns a
string. That string is built by iterating over a hash.

Thanks.
 
S

Siep Korteling

Ben said:
I didn't need them by insertion order, just to be consistent. I
apologize if I made that unclear.

Also, I am not testing a hash, I am testing a method that returns a
string. That string is built by iterating over a hash.

Thanks.

This code fills a hash and then iterates the hash to build a string.

h={}
("a".."z").each{|i| h = i}
10.times do
res = h.inject(""){|str, pair| str<<pair[1]}
puts res
end

The string is always "vkwlaxmbynczodpeqfrgshtiuj" on my machine. There
is no visible logic, but it is predictable and testable. However, the
test may fail when you install the next ruby version, or perhaps when
run on a another platform.
I don't know why your code behaves differently, but if the insertion
order in the hash is not predictable (threads ?), then an ordered hash
won't help.

regards,

Siep
 
E

Erik Hollensbe

Lex said:
Ben said:
Because for performance it's bad. I don't care if performance is bad in
my tests. Which is why it would be nice to alter how hashes work ONLY in
my test environment. But sorting by keys isn't smart either.

NoMethodError: undefined method `<=>' for :a:Symbol
from (irb):16:in `sort'
from (irb):16
from :0

Here's a hack for altering the original Hash class :

class Hash
alias :eek:ld_equals :[]=
attr_reader :eek:rdered_values

def []=(key,value)
@ordered_values ||= []
@ordered_values << key
old_equals(key,value)
end


end

hsh = {}
hsh["a"]="b"
hsh["b"]="c"
hsh.ordered_values.each do |key|
puts key
end

hsh.delete("a")
hsh.ordered_values.each do |key|
puts key
end

Does not output what you want. :)

There are a number of recommended packages already that should do this,
use one of them.. Not to mention this monkeypatch is going to slow down
your whole program, when you likely just need it in one or two spots.

If you do have to code it yourself, consider using the 'delegate'
library (in stdlib) to wrap your specific, non-standard logic into its
own class, where it can be managed separately from standard hashes.

Yours in preventing senseless monkeypatching,

-Erik
 
B

Ben Johnson

Erik said:
There are a number of recommended packages already that should do this,
use one of them.. Not to mention this monkeypatch is going to slow down
your whole program, when you likely just need it in one or two spots.

If you do have to code it yourself, consider using the 'delegate'
library (in stdlib) to wrap your specific, non-standard logic into its
own class, where it can be managed separately from standard hashes.

Yours in preventing senseless monkeypatching,

-Erik

Thanks Erik, I agree 100%. I ONLY applied this when testing, where
performance really isn't an issue. I just needed hashes to iterate in a
consistent order. What was unique about my situation is that the hash
order didn't matter to the functional purpose of the method. It mattered
only when I needed to do assertions in my tests.

Honestly, if order if meaningful you should use an array. I think any of
the above gems are not the best choice when it comes to performance.
 
E

Erik Hollensbe

Siep said:
The string is always "vkwlaxmbynczodpeqfrgshtiuj" on my machine. There
is no visible logic, but it is predictable and testable. However, the
test may fail when you install the next ruby version, or perhaps when
run on a another platform.
I don't know why your code behaves differently, but if the insertion
order in the hash is not predictable (threads ?), then an ordered hash
won't help.

Hash ordering is generally deterministic on read until another write
occurs. Then it all depends on the algorithm used to hash the key, if
the bucket size was changed and rekeying occured, a number of things.
The RHG I believe goes into how hashing algorithms work in ruby, but a
good example I can definitely cite is perl 5.6 (and prior)'s hashing
algorithm, which if seeded properly could cause it to resize the buckets
and re-key so often it could bring system load to DoS levels.... Yes,
that's one program, with one hash, with a lot of pairs inserted one at a
time, specifically ordered to cause the algorithm to internally resize
and rekey on each insert (you know, for performance). There's a bugtraq
posting from ... 2002? 2003? that goes into the specific method if
you're interested (and yes, it was fixed in 5.8).

Anyways, that's not intended as a dig on perl, but basically it's not
something to count on, ever.

-Erik
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,535
Members
45,007
Latest member
obedient dusk

Latest Threads

Top