Forum: Ruby lazy lookup

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
2cb736dc6ab6b986406912b22dff9ea7?d=identicon&s=25 Thomas Hafner (Guest)
on 2009-05-02 15:40
(Received via mailing list)
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 method ``lookup'' 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 call ``h[k]'' implies calling the
lambda, for the next times ``h[k]'' implies accessing the hash.

(Tried with Ruby 1.8.6.)

Regards
  Thomas
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (Guest)
on 2009-05-02 16:25
(Received via mailing list)
On 02.05.2009 15:33, Thomas Hafner wrote:
>     self.class.class_eval{ define_method(:lookup){ |k| h[k] } }
>   end
>
> Once ``define_lookup'' has been called, the method ``lookup'' 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 call ``h[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
2cb736dc6ab6b986406912b22dff9ea7?d=identicon&s=25 Thomas Hafner (Guest)
on 2009-05-04 16:42
(Received via mailing list)
Robert Klemme <shortcutter@googlemail.com> wrote/schrieb
<763388F1amqkuU1@mid.individual.net>:

> 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
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (Guest)
on 2009-05-04 17:04
(Received via mailing list)
2009/5/4 Thomas Hafner <thomas@faun.hafner.nl.eu.org>:
> 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
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (Guest)
on 2009-05-04 17:05
(Received via mailing list)
2009/5/4 Robert Klemme <shortcutter@googlemail.com>:
>> 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
This topic is locked and can not be replied to.