[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

A use case for an ordered hash

Hal E. Fulton

8/13/2006 6:31:00 AM

There have been numerous occasions when I wanted an
ordered hash, but usually I can't remember to write
them down.

Here's just one.

Once I wanted a "dynamic case statement" of sorts.
I wanted to use procs as values in a hash. Something
like:

actions = { /abcd/ => lambda { do_this },
/xyz/ => lambda { do_that },
/abc/ => lambda { other }}

Then I could just iterate through the keys looking
for a match, then use that key to find the associated
proc and call it.

However, I quickly noticed that this is no good. The
order of iteration is unpredictable. So I couldn't
guarantee that (for example) /abcd/ would be tested
before /abc/, and so on.

So yeah, I ended up using an array of arrays. But it
just felt wrong.


Hal

90 Answers

M. Edward (Ed) Borasky

8/13/2006 6:43:00 AM

0

Hal Fulton wrote:
> There have been numerous occasions when I wanted an
> ordered hash, but usually I can't remember to write
> them down.
>
> Here's just one.
>
> Once I wanted a "dynamic case statement" of sorts.
> I wanted to use procs as values in a hash. Something
> like:
>
> actions = { /abcd/ => lambda { do_this },
> /xyz/ => lambda { do_that },
> /abc/ => lambda { other }}
>
> Then I could just iterate through the keys looking
> for a match, then use that key to find the associated
> proc and call it.
>
> However, I quickly noticed that this is no good. The
> order of iteration is unpredictable. So I couldn't
> guarantee that (for example) /abcd/ would be tested
> before /abc/, and so on.
>
> So yeah, I ended up using an array of arrays. But it
> just felt wrong.
>
>
> Hal
>
>

That sort of code is what Chuck Moore evolved into Forth. :) Seriously,
though, isn't there a way to do this with a single array? Store the
patterns followed by the lambdas in sequence. Find the first index that
matches the pattern and take the lambda at [index+1]? Or two parallel
arrays? I use two parallel arrays for this sort of thing a lot -- it's
easy for me to read, given my FORTRAN background. :)

Hal E. Fulton

8/13/2006 7:04:00 AM

0

M. Edward (Ed) Borasky wrote:
>
> That sort of code is what Chuck Moore evolved into Forth. :) Seriously,
> though, isn't there a way to do this with a single array? Store the
> patterns followed by the lambdas in sequence. Find the first index that
> matches the pattern and take the lambda at [index+1]? Or two parallel
> arrays? I use two parallel arrays for this sort of thing a lot -- it's
> easy for me to read, given my FORTRAN background. :)
>

Yes, I could use a single flat array... I could also program
in FORTRAN... ;)


Hal

Francis Cianfrocca

8/13/2006 7:48:00 AM

0

On 8/13/06, Hal Fulton <hal9000@hypermetrics.com> wrote:
> actions = { /abcd/ => lambda { do_this },
> /xyz/ => lambda { do_that },
> /abc/ => lambda { other }}
>


actions.keys.sort {|a,b| b.to_s <=> a.to_s}.each {|k|
if k =~ your_string
actions[k].call
break
end
}

A more-greedy regex sorts lexicographically behind a head-matching
less-greedy one.

Hal E. Fulton

8/13/2006 8:02:00 AM

0

Francis Cianfrocca wrote:
> On 8/13/06, Hal Fulton <hal9000@hypermetrics.com> wrote:
>
>> actions = { /abcd/ => lambda { do_this },
>> /xyz/ => lambda { do_that },
>> /abc/ => lambda { other }}
>>
>
>
> actions.keys.sort {|a,b| b.to_s <=> a.to_s}.each {|k|
> if k =~ your_string
> actions[k].call
> break
> end
> }
>
> A more-greedy regex sorts lexicographically behind a head-matching
> less-greedy one.
>

Clever workaround. Thanks for that.

There are all kinds of workarounds for situations like these.

I don't *need* an ordered hash. I don't even *need* a case
statement, as I could do the same thing with a bunch of ifs.
And so on.


Hal

Robert Klemme

8/13/2006 8:22:00 AM

0

Hal Fulton wrote:
> There have been numerous occasions when I wanted an
> ordered hash, but usually I can't remember to write
> them down.
>
> Here's just one.
>
> Once I wanted a "dynamic case statement" of sorts.
> I wanted to use procs as values in a hash. Something
> like:
>
> actions = { /abcd/ => lambda { do_this },
> /xyz/ => lambda { do_that },
> /abc/ => lambda { other }}
>
> Then I could just iterate through the keys looking
> for a match, then use that key to find the associated
> proc and call it.
>
> However, I quickly noticed that this is no good. The
> order of iteration is unpredictable. So I couldn't
> guarantee that (for example) /abcd/ would be tested
> before /abc/, and so on.
>
> So yeah, I ended up using an array of arrays. But it
> just felt wrong.

To me a Hash feels wrong. Why? Because you don't make any use of hash
properties for fast lookup. You just iterate in plain order.

Kind regards

robert

Matt Todd

8/13/2006 9:31:00 AM

0

Absolutely... an Array is perfect for ordered information.

actions = [
{ /abcd/ => lambda { do_this } },
{ /xyz/ => lambda { do_that } },
{ /abc/ => lambda { other } }
]

Then, all you do is...

actions.select { |action| action[input] }

where input = /xyz/ you will receive the appropriate hash (key and
Proc) in an array. So, to do something with that, you could...

actions.select { |action| action[input] }.first[input].call

which calls the Proc associated with the matching element.

I'm not too thrilled with its convolutedness, and I'm sure there's a
better way, but I'm pretty sleepy, so this is the best I'm coming up
with right now. :) As always, you can write a method to hide the gory
details.

def exec_route input
actions.select { |action| action[input] }.first[input].call
end

and just call:

exec_route /xyz/

and blam! do_that is executed!

Hope this helps.

M.T.

Mauricio Fernández

8/13/2006 10:04:00 AM

0

On Sun, Aug 13, 2006 at 06:31:07PM +0900, Matt Todd wrote:
> Absolutely... an Array is perfect for ordered information.
>
> actions = [
> { /abcd/ => lambda { do_this } },
> { /xyz/ => lambda { do_that } },
> { /abc/ => lambda { other } }
> ]
>
> Then, all you do is...
>
> actions.select { |action| action[input] }
>
> where input = /xyz/ you will receive the appropriate hash (key and
> Proc) in an array. So, to do something with that, you could...
>
> actions.select { |action| action[input] }.first[input].call
>
> which calls the Proc associated with the matching element.

That's not what he wants though (the point is matching against the Regexp ---
Hash#[] doesn't buy you anything there).

Maybe

require 'enumerator'
def ASSOC(x); x.enum_for(:each_slice, 2).to_a end

actions = ASSOC [
/abcd/, lambda{ 1 },
/xyz/, lambda{ 2 },
/abc/, lambda{ 3 }
]

# Then he's got to

def run(command, actions)
actions.each{|re, action| return action[command] if re =~ command}
nil
end

run("abcdef", actions) # => 1
run("abcf", actions) # => 3


# But this would be nicer
class Array
def cassoc(x) # "case assoc"
each{|arr| return arr if arr[0] === x}
nil
end
end

re, action = actions.cassoc("abcdef")
action and action.call # => 1
re, action = actions.cassoc("abcx")
action and action.call # => 3


--
Mauricio Fernandez - http://eige... - singular Ruby

James Gray

8/13/2006 3:27:00 PM

0

On Aug 13, 2006, at 3:25 AM, Robert Klemme wrote:

> Hal Fulton wrote:
>> There have been numerous occasions when I wanted an
>> ordered hash, but usually I can't remember to write
>> them down.
>> Here's just one.
>> Once I wanted a "dynamic case statement" of sorts.
>> I wanted to use procs as values in a hash. Something
>> like:
>> actions = { /abcd/ => lambda { do_this },
>> /xyz/ => lambda { do_that },
>> /abc/ => lambda { other }}
>> Then I could just iterate through the keys looking
>> for a match, then use that key to find the associated
>> proc and call it.
>> However, I quickly noticed that this is no good. The
>> order of iteration is unpredictable. So I couldn't
>> guarantee that (for example) /abcd/ would be tested
>> before /abc/, and so on.
>> So yeah, I ended up using an array of arrays. But it
>> just felt wrong.
>
> To me a Hash feels wrong. Why? Because you don't make any use of
> hash properties for fast lookup. You just iterate in plain order.

He *is* making use of a Hash property. He wants the keys to be
unique. None of the other solutions shown in this thread have
addressed that.

James Edward Gray II


Mauricio Fernández

8/13/2006 4:16:00 PM

0

On Mon, Aug 14, 2006 at 12:27:07AM +0900, James Edward Gray II wrote:
> On Aug 13, 2006, at 3:25 AM, Robert Klemme wrote:
> >Hal Fulton wrote:
> >>Once I wanted a "dynamic case statement" of sorts.
> >>I wanted to use procs as values in a hash. Something
> >>like:
> >> actions = { /abcd/ => lambda { do_this },
> >> /xyz/ => lambda { do_that },
> >> /abc/ => lambda { other }}
[...]
> >To me a Hash feels wrong. Why? Because you don't make any use of
> >hash properties for fast lookup. You just iterate in plain order.
>
> He *is* making use of a Hash property. He wants the keys to be
> unique. None of the other solutions shown in this thread have
> addressed that.

Hal is making the case for *literal* "ordered hashes". The keys will be
unique because he's writing them himself manually. I don't think he wants to
make use of this:

actions = { /a/ => 1, # <--
/b/ => 2, # /a/ => 4 # <--+-- why do this in the first place.
}
actions[/a/] # => 4

The way he uses the structure is reminiscent of Array#assoc. The only thing
a Hash has going for it in this case is the cleaner notation and the arrow.
But
actions = ASSOC [/abcd/, lambda{ ... },
/abc/, lambda{ ... } ]
(using parentheses if you want) looks good enough for me, and something like
the Array#cassoc I showed before would do fine in this case.

--
Mauricio Fernandez - http://eige... - singular Ruby

Hal E. Fulton

8/13/2006 7:02:00 PM

0

Robert Klemme wrote:
>>
>> So yeah, I ended up using an array of arrays. But it
>> just felt wrong.
>
>
> To me a Hash feels wrong. Why? Because you don't make any use of hash
> properties for fast lookup. You just iterate in plain order.
>

I never use a hash for fast lookup. I use it because it
allows me easily to associate an arbitrary key with a
value.

Hal