Why Does Hash Apparently Reorder Its Internal Representation And Other Associated Ponderings

Hi,

···

In message "Re: Why Does Hash Apparently Reorder Its Internal Representation And Other Associated Ponderings" on Mon, 21 Aug 2006 19:26:10 +0900, "Robert Dober" <robert.dober@gmail.com> writes:

I am just dreaming:

ohhhhhh = OrderedHash.new { |k1,k2| tell_my_friend_what_order_is(k1, k2) }

class MyOH < OrderedHash
      def key_order(k1, k2) ### Here we could define Mixins for common key
orders

And would asking for having two like the following be too much?

I think it's not just "ordered" but "sorted".

              matz.

Phrogz wrote:

Austin Ziegler wrote:

PHP's "array" is an associative array allowing "hash"-like handling
with an ordered iteration. That's probably the source of 99% of the
reasons that people want it.

And, for what it's worth, JavaScript's Object primitive is also an
associative array that retains insertion order for iterating keys,
while exhibiting performance characteristics of a hash.

I find that interesting, since so many appear convinced that
a so-called ordered hash would be "too slow."

Do you know anything about the internal implementation?

Hal

Isak wrote:

I don't think 'ordered' is a good name for something sorted by _insertion order_.

The fact that something can be "sorted" at all is only because it
has an order, i.e., is sequential.

In fact, I would say an "ordered hash" would be subject to being
sorted just as an array is. (We can sort an array because it has
an order -- first element, second element, and so on. We can "sort"
a regular hash, but we get an array back.)

I am probably 'tainted' by my exposure to the Java API, but just like I expect an ordered tree to be sorted by value, I'd expect an ordered map to be sorted by key.

Insertion order maps are very useful too, and I'd love to see both added to the Ruby stdlib. I(o?)Hash and OHash? Let's keep Hash as lean and mean as possible.

That seems reasonable to me.

I once proposed the name "Map" for such a class -- there was some reason
this wasn't considered good, but I can't recall why.

Hal

Phrogz wrote:

And, for what it's worth, JavaScript's Object primitive is also an
associative array that retains insertion order for iterating keys,
while exhibiting performance characteristics of a hash.

Clarification: apparently the ECMAScript spec[1] don't actually require
that the insertion order be preserved; it's just that the major JS
engine implementations happen to do so. On the order of properties, the
spec actually says:

"Step 5: Get name of the next property [...] that doesn't have the
DontEnum attribute.
[...]
The mechanics of enumerating the properties (step 5 in the first
algorithm, step 6 in the second) is implementation dependent. The order
of enumeration is defined by the object. Properties of the object being
enumerated may be deleted during enumeration. If a property that has
not yet been visited during enumeration is deleted, then it will not be
visited. If new properties are added to the object being enumerated
during enumeration, the newly added properties are not guaranteed to be
visited in the active enumeration."

[1]
http://www.ecma-international.org/publications/files/ecma-st/ECMA-262.pdf

Bil Kleb wrote:

Hal Fulton wrote:
>
> My use case (I started that thread) may not be compelling. Other people,
> including Bil Kleb, have said it would be useful to them also.

So far, I've managed to duplicate the functionality of two
Java codes having 834 and 1084 lines of codes with 24 and 55
lines of Ruby, respectively. The second one is so long[1]
due to the lack of ordered Hashes

Did you try association lists?

class Array
  def to_assoc
    f=nil ; partition{f=!f}.transpose
  end
  def set k, v
    (pair = assoc(k)) ? self[ index(pair) ] = [k,v] : push( [k,v] )
  end
  def rm k
    (pair = assoc( k )) && slice!( index( pair ) )
  end
end

a = [:foo,22, :bar,33, :baz,44].to_assoc
a.set( :bar, 99 )
a.set :yes, -1
a.rm :baz
p a

Hi,

>I am just dreaming:
>
>ohhhhhh = OrderedHash.new { |k1,k2| tell_my_friend_what_order_is(k1, k2)
}
>
>class MyOH < OrderedHash
> def key_order(k1, k2) ### Here we could define Mixins for common
key
>orders
>
>And would asking for having two like the following be too much?

I think it's not just "ordered" but "sorted".

Sorry if I was unclear - just dreaming U know.
My naming is bad, usually, often, always?

So I was just wondering if an OrderedHash would just define the order and
sorting would be done when necessary.
A SortedHash would keep track of the keys in a sorted way and sorted access
would be fast.
To express better what I had in mind was (updated with your naming),
expressed in a language I talk better than English, Ruby (1)

s = SortedHash ...
o = OrderdHash ...

s[:fourty_two] = 1764 # internal key order will be updated
o[:fourty_two] = 1764 # nothing to be done

s.each # no presorting needed
o.each or o.each_sorted or o.each_ordered # presorting needed

Robert

(1) That does not mean I cannot talk nonsense in Ruby though :wink:

                                                        matz.

···

On 8/21/06, Yukihiro Matsumoto <matz@ruby-lang.org> wrote:

In message "Re: Why Does Hash Apparently Reorder Its Internal > Representation And Other Associated Ponderings" > on Mon, 21 Aug 2006 19:26:10 +0900, "Robert Dober" < > robert.dober@gmail.com> writes:

--
Deux choses sont infinies : l'univers et la bêtise humaine ; en ce qui
concerne l'univers, je n'en ai pas acquis la certitude absolue.

- Albert Einstein

Hashes are typically implemented with a number of buckets pre-allocated,
usually in the form of an array. Hash values are calculated on the keys to
be inserted, and a bucket is chosen mod bucket_count. So it's pretty much
anyone's guess where a given hash will put a key. The apparent reordering is
actually not quite correct; what was seen is apparent *ordering*. The fact
that the insertion order was retained was only a lucky coincidence. Try
inserting a, b, and c in a different order.

To make matters worse, a hash will frequently have collisions when choosing
a bucket, so you generally also have a linked list in each bucket to hold
values that collide. Then if there are n elements with the same hash mod
bucket_count, you'll have to iterate over them looking for the correct
element (in Ruby's case, I believe the one that matches eq?). To avoid too
many collisions impacting hash performance, it may be necessary to expand
the number of buckets and rehash everything...at a cost of n, where n is the
number of elements inserted up to that point. After rehashing, all apparent
ordering will likely have disappeared.

The general associative structure is called a map, not a hash, because it
maps keys to values. A hash is one way to implement a map that gives
insertion and lookup performance guarantees; specifically, values can be
inserted, removed, and queries in constant time (as opposed to some
polynomial of n elements). The trade-off is that order is not guaranteed,
since order ends up being determined by the hash values.

You can implement a map that preserves or maintains order in multiple ways;
you can use a tree--or specifically a "red/black tree", which is memory
optimal but requires at most log(n) time a given value, and something like
log(n) to rebalance the tree periodically. The characteristic used to
maintain order (and balance) in the tree is not important. You can also
maintain a hash and an ordered list internally, using the list for iteration
and the hash for all other operations. Hash lookups remain constant time but
modifications take some polynomial of n (because you now have to update the
ordered list).

It's all about tradeoffs. Array is a minimum implementation of an ordered
list...in that it maintains the order you provide for it. It is not a sorted
list, which maintains an order based on some external characteristic (like a
sort algorithm or <=>). Hash is a minimal implementation of an unordered
map, which provides fast access to named keys but does not preserve order or
perform any sorting.

Those calling for an ordered hash are really looking for an ordered map, and
it seems in many cases they want a sorted map (or an ordered or sorted
"associative array", as others have mentioned...tomato/tomato). Hash should
not be modified to preserve order, since it would add overhead to many, many
cases where order is unnecessary additional overhead. I wouldn't be opposed
to adding a general-purpose Tree, to allow those who want to implement an
ordered map a fast, native data structure to do so; but I would oppose
adding uncommon variants of hash to the standard libraries, specifically the
OHash (and IHash?!?) that have come up a few times.

···

--
Contribute to RubySpec! @ www.headius.com/rubyspec
Charles Oliver Nutter @ headius.blogspot.com
Ruby User @ ruby.mn
JRuby Developer @ www.jruby.org
Application Architect @ www.ventera.com

Hal Fulton wrote:

Phrogz wrote:
> And, for what it's worth, JavaScript's Object primitive is also an
> associative array that retains insertion order for iterating keys,
> while exhibiting performance characteristics of a hash.

I find that interesting, since so many appear convinced that
a so-called ordered hash would be "too slow."

Do you know anything about the internal implementation?

I don't. I wouldn't call most JavaScript engines 'fast', but I suspect
that has to do with aspects other than the native Object type.

Without having tried it, I would think that preserving insertion order
would be a (small) memory size addition to each hash (as you've
mentioned before), a *tiny* slowdown in the speed to store a new entry,
and no performance change in accessing an entry.

Anyhow, because I'm too lazy to do so, you (or someone else) can get
source code for the SpiderMonkey[1] and Rhino[2] and check out how
insertion order is preserved on Object.

[1] http://www.mozilla.org/js/spidermonkey/ - the C-based JS engine
[2] http://www.mozilla.org/rhino/ - the Java-based JS engine

Hal Fulton wrote:

Isak wrote:

I don't think 'ordered' is a good name for something sorted by _insertion order_.

The fact that something can be "sorted" at all is only because it
has an order, i.e., is sequential.

In fact, I would say an "ordered hash" would be subject to being
sorted just as an array is. (We can sort an array because it has
an order -- first element, second element, and so on. We can "sort"
a regular hash, but we get an array back.)

You're right. Ordered merely means that elements have a position, I had it confused with 'sorted'.

I am probably 'tainted' by my exposure to the Java API, but just like I expect an ordered tree to be sorted by value, I'd expect an ordered map to be sorted by key.

Insertion order maps are very useful too, and I'd love to see both added to the Ruby stdlib. I(o?)Hash and OHash? Let's keep Hash as lean and mean as possible.

That seems reasonable to me.

I once proposed the name "Map" for such a class -- there was some reason
this wasn't considered good, but I can't recall why.

Yup, associated arrays are maps, not hashes.

After reading the nutter's on google groups (hate the broken nntp<->mailing list concept), I realize that calling them hashes isn't appropriate. Once you change their caracteristics they aren't really hashes any more.

SortedMap (or TreeMap) and (Insertion)OrderedMap (or perhaps LinkedHashMap; doubly linked list + hash) are probably better names..?

Isak

···

Hal

Isak wrote:
>
> I don't think 'ordered' is a good name for something sorted by
> _insertion order_.

The fact that something can be "sorted" at all is only because it
has an order, i.e., is sequential.

Uh, no.

Whether it can be sorted depends on whether the contents are comparable.

irb(main):004:0> class Foo
irb(main):005:1> end
=> nil
irb(main):006:0> [Foo.new, Foo.new].sort
NoMethodError: undefined method `<=>' for #<Foo:0xb7dfe658>
        from (irb):6:in `sort'
        from (irb):6

···

On 8/21/06, Hal Fulton <hal9000@hypermetrics.com> wrote:
        from :0

--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/

William James wrote:

Did you try association lists?

Sort of, but I liked the interface of Hash too much
to abandon it. So far, I am carrying along an array
of keys in order of creation for the one place that
I need it. Otherwise, I have the beauty of the stock
Hash interface at my disposal.

Speed is not an issue (for me). The 5,000 simulations
I am running take days to run. Even if the Ruby I am
use to set them up takes 5 minutes instead of 5 seconds,
I'll take the beauty of an ordered Hash over association
lists any day.

Regards,

···

--
Bil
http://fun3d.larc.nasa.gov

Phrogz wrote:

I don't. I wouldn't call most JavaScript engines 'fast', but I suspect
that has to do with aspects other than the native Object type.

Without having tried it, I would think that preserving insertion order
would be a (small) memory size addition to each hash (as you've
mentioned before), a *tiny* slowdown in the speed to store a new entry,
and no performance change in accessing an entry.

Anyhow, because I'm too lazy to do so, you (or someone else) can get
source code for the SpiderMonkey[1] and Rhino[2] and check out how
insertion order is preserved on Object.

[1] http://www.mozilla.org/js/spidermonkey/ - the C-based JS engine
[2] http://www.mozilla.org/rhino/ - the Java-based JS engine

Haha... I've put that in my notes. That's all I guarantee right now.

Don't ever get in a laziness contest with a master.

Hal

> I don't think 'ordered' is a good name for something sorted by
> _insertion order_.

The fact that something can be "sorted" at all is only because it
has an order, i.e., is sequential.

Uh, no.

Whether it can be sorted depends on whether the contents are comparable.

Both are partly true. The sorting depends on comparable, but if the data structure is not sequencable (for lack of a better term) the results of sorting will need to be returned in something that is (such as an Array) rather than updated in place. In other words, you could implement #sort, but not #sort!.

Matthew

Rick DeNatale wrote:

···

On 8/21/06, Hal Fulton <hal9000@hypermetrics.com> wrote:

Isak wrote:
>
> I don't think 'ordered' is a good name for something sorted by
> _insertion order_.

The fact that something can be "sorted" at all is only because it
has an order, i.e., is sequential.

Uh, no.

Whether it can be sorted depends on whether the contents are comparable.

I wasn't speaking in a Ruby sense. And even if I were, there
are other ways of sorting besides calling the sort method.

Hal

You can do this automatically (if you aren't already) by creating an
object that holds a hash and an array, defines =, each and delete to
do the right thing, and delegates missing methods to the hash.

m.

···

On 8/21/06, Bil Kleb <Bil.Kleb@nasa.gov> wrote:

Sort of, but I liked the interface of Hash too much
to abandon it. So far, I am carrying along an array
of keys in order of creation for the one place that
I need it. Otherwise, I have the beauty of the stock
Hash interface at my disposal.

Indeed. There is a legitimate use case of having both fast random
access on an arbitrary object (in my case a Page object) and insertion
ordered processing when iterating. Speed is an issue for me, but I
think people are trying to prematurely optimize. Where PDF::Writer is
slow, it is not because of an ordered hash.

-austin

···

On 8/21/06, Bil Kleb <Bil.Kleb@nasa.gov> wrote:

William James wrote:
>
> Did you try association lists?
Sort of, but I liked the interface of Hash too much
to abandon it. So far, I am carrying along an array
of keys in order of creation for the one place that
I need it. Otherwise, I have the beauty of the stock
Hash interface at my disposal.

--
Austin Ziegler * halostatue@gmail.com * http://www.halostatue.ca/
               * austin@halostatue.ca * You are in a maze of twisty little passages, all alike. // halo • statue
               * austin@zieglers.ca

Martin DeMello wrote:

···

On 8/21/06, Bil Kleb <Bil.Kleb@nasa.gov> wrote:

Sort of, but I liked the interface of Hash too much
to abandon it. So far, I am carrying along an array
of keys in order of creation for the one place that
I need it. Otherwise, I have the beauty of the stock
Hash interface at my disposal.

You can do this automatically (if you aren't already) by creating an
object that holds a hash and an array, defines =, each and delete to
do the right thing, and delegates missing methods to the hash.

There are any number of ways to do this sort of thing.
But they all suffer from not having a convenient notation
for literals.

Hal

Martin DeMello wrote:

···

On 8/21/06, Bil Kleb <Bil.Kleb@nasa.gov> wrote:

You can do this automatically (if you aren't already) by creating an
object that holds a hash and an array, defines =, each and delete to
do the right thing, and delegates missing methods to the hash.

Hmmm... good idea. I've largely missed out on the
whole method_missing idiom. Sounds like a good use.

I'll try to look into it after I return from JPL next
week. However, if you'd like to throw down an example,
I might be able to work it in now...

Thanks,
--
Bil
http://fun3d.larc.nasa.gov

Quick proof of concept:

require 'enumerator'

class OHash
  include Enumerable

  def initialize
    @a =
    @h = {}
  end

  def =(k,v)
    @a.delete(k) if @h[k]
    @h[k] = v
    @a << k
  end

  def delete(k, &blk)
    @a.delete(k)
    @h.delete(k)
  end

  def each
    p @a, @h
    each_key {|k| yield [k, @h[k]]}
  end

  def each_key
    @a.each {|k| yield k}
  end

  def method_missing(*args)
    @h.send(*args)
  end
end

def o(*ary)
  r = OHash.new
  ary.each_slice(2) {|k,v| r[k] = v }
  r
end

# testing
a = o("hello", "world", :foo, "bar", 5, 6)
a.each {|k,v| p [k,v]}
puts a["hello"]
a["hello"] = 5
a.each {|k,v| p [k,v]}

martin

···

On 8/21/06, Bil Kleb <Bil.Kleb@nasa.gov> wrote:

Martin DeMello wrote:
> On 8/21/06, Bil Kleb <Bil.Kleb@nasa.gov> wrote:
>
> You can do this automatically (if you aren't already) by creating an
> object that holds a hash and an array, defines =, each and delete to
> do the right thing, and delegates missing methods to the hash.

Hmmm... good idea. I've largely missed out on the
whole method_missing idiom. Sounds like a good use.

I'll try to look into it after I return from JPL next
week. However, if you'd like to throw down an example,
I might be able to work it in now...

Martin DeMello wrote:

Quick proof of concept:

Thanks!

Later,

···

--
Bil
http://fun3d.larc.nasa.gov