Lazy lookup


#1

Hello,

how about the following implementation for lazy lookups?

def define_lookup
h = lambda{ |k|
h = {}
# … fill h (expensive!) …
h[k]
}
self.class.class_eval{ define_method(:lookup){ |k| h[k] } }
end

Once define_lookup'' has been called, the methodlookup’’ is
defined and will act in a lazy manner. Just call lookup'' as often as you need, but only for the first time the expensive operation (e.g. dealing with files) will be performed. Due to duck typing no condition needs to be checked: for the first callh[k]’’ implies calling the
lambda, for the next times ``h[k]’’ implies accessing the hash.

(Tried with Ruby 1.8.6.)

Regards
Thomas


#2

On 02.05.2009 15:33, Thomas H. wrote:

self.class.class_eval{ define_method(:lookup){ |k| h[k] } }

end

Once define_lookup'' has been called, the methodlookup’’ is
defined and will act in a lazy manner. Just call lookup'' as often as you need, but only for the first time the expensive operation (e.g. dealing with files) will be performed. Due to duck typing no condition needs to be checked: for the first callh[k]’’ implies calling the
lambda, for the next times ``h[k]’’ implies accessing the hash.

(Tried with Ruby 1.8.6.)

Sorry to sound discouraging but there is memoize already. The
difference is that not the whole Hash is filled but only the one that
you need for the argument list provided which seems potentially more
efficient.

define_lookup should probably be a class method but in that case having
a single Hash for all instances is problematic.

Note, that you can use Hash’s default_proc to achieve something similar
very elegantly:

class Foo
def initialize(some, args)
@some = some
@args = args
@lookup = Hash.new {|h,k| h[k] = do_lookup(k)}
end

def lookup(a,b)
@lookup[[a,b]]
end

private
def do_lookup(args)
puts “calculating…”
sleep 2
args.first + args.last + @some + @args
end
end

Note that you do not need a separate method but if the calculation is
complex code might be more readable with another method.

irb(main):059:0> f = Foo.new 1,2
=> #<Foo:0x101b3ce8 @some=1, @args=2, @lookup={}>
irb(main):060:0> f.lookup 3,4
calculating…
=> 10
irb(main):061:0> f.lookup 3,4
=> 10
irb(main):062:0>

In case you want to fill the Hash completely you can just replace the
calculation code to assign multiple values.

OTOH, often a single Hash with a default_proc is sufficient already,
e.h.

irb(main):064:0> foo = Hash.new {|h,k| h[k] = k + 1 + 3}
=> {}
irb(main):065:0> foo[3]
=> 7
irb(main):066:0> foo
=> {3=>7}

Kind regards

robert


#3

Robert K. removed_email_address@domain.invalid wrote/schrieb
removed_email_address@domain.invalid:

Sorry to sound discouraging but there is memoize already. The
difference is that not the whole Hash is filled but only the one
that you need for the argument list provided which seems potentially
more efficient.

In my situation this ist not the case. The key value pairs can be
retrieved by reading a file. It’s better to open/read/close the file
only once and retrieve all key value pairs at the same time. Of course
the file should not be read at all, if never a value will be
requested.

In case you want to fill the Hash completely you can just replace the
calculation code to assign multiple values.

How does that look like? Somethink like that?:

Hash.new{ |h,k| h.replace(
COMPLETELY_CALCULATED_HASH
); h[k] }

(COMPLETELY_CALCULATED_HASH should be replaced by some code which
calculates the hash completely)

Regards
Thomas


#4

2009/5/4 Robert K. removed_email_address@domain.invalid:

How does that look like? Somethink like that?:

Hash.new{ |h,k| h.replace(
COMPLETELY_CALCULATED_HASH
); h[k] }

PS: careful with h[k] at the end: you might create an infinite loop
unless you are sure that k is in the Hash.

Cheers

robert


#5

2009/5/4 Thomas H. removed_email_address@domain.invalid:

the file should not be read at all, if never a value will be
requested.

In case you want to fill the Hash completely you can just replace the
calculation code to assign multiple values.

How does that look like? Somethink like that?:

Hash.new{ |h,k| h.replace(
COMPLETELY_CALCULATED_HASH
); h[k] }

(COMPLETELY_CALCULATED_HASH should be replaced by some code which
calculates the hash completely)

I don’t see the need for #replace. You can as well hand over h to the
routine that fetches the data. But otherwise yes. As a safety
measure you would probably also remember that you have loaded the Hash
in an instance variable of h.

Kind regards

robert