mojodaddy has asked for the wisdom of the Perl Monks concerning the following question:

Let's say I have 10 widgets:

my @widgets = qw( foo bar baz blort blah plugh xyzzy arfle barfle gloop );

I need to have 3 of them running all the time, but they get tired and need to rest, so when I start my day I pick the three most rested and put them to work, and then when one gets tired I need to replace it with the next most rested one that isn't already running. They all have different "rest" values, and they tire and rejuvenate at different rates, so the most rested one now might not be the most rested one later. But that's OK because whenever I need one I can just do a database query that will rank them in order of most rested, 2nd most, etc. Unfortunately, it won't tell me if the most rested bugger is already running or not.

So lets say I start the day with:

my @running = qw(foo bar baz);

After a while foo gets tired and needs a break. The next widget in the queue is $w. So, what is the best way to check whether $w is an element of @running?

Currently I have something like:

# get the top 3 candidates from the db while (my $wref = $sth->fetchall_arrayref([0], 3)){ my $w = $wref->[0]; foreach my $r (@running){ unless ($r eq $w){ # put $w to work } else{ # try the next $w } } }

Which is fine, I guess, since I only have to check at most two elements against 9 or 10 or whatever; but it seems to me that once we get into bigger numbers, that technique could take months!

From what I read about slices it seemed I'd need to know what position the element had in the array. I experimented with sort and got really confused, and once I read How to compare arrays? (xmms alarm clock) and Array::Compare I started to realize that maybe this wasn't as simple a problem as I thought.

If anyone has some magic to share on this, I'd sure appreciate it.

Replies are listed 'Best First'.
Re: comparing array elements
by jdporter (Paladin) on Mar 30, 2007 at 15:31 UTC

    How about, instead of using an array to hold the set of currently running widgets, use a hash. The hash (as I propose it) would have an entry for all of the widgets; the corresponding value would be a boolean indicating whether the thing is running or not. So instead of shuffling widget IDs into and out of a @running array, you just check/set/clear the hash values.

    Here's the idea, neatly (though gratuitously) encapsulated:

    { package WidgetPool; my %widget_state; sub init { @widget_state{ @_ } = (0) x @_; } sub set_running { @widget_state{ @_ } = (1) x @_; } sub replace { my( $out, $in ) = @_; $widget_state{$out} = 0; $widget_state{$in} = 1; } sub is_running { my( $w ) = @_; $widget_state{$w} } } my @widgets = qw( foo bar baz blort blah plugh xyzzy arfle barfle gloo +p ); my @running = qw( foo bar baz ); WidgetPool::init( @widgets ); WidgetPool::set_running( @running ); # stand down 'foo', stand up $w: WidgetPool::is_running('foo') or croak "Um, 'foo' not running.\n"; WidgetPool::replace( 'foo', $w );

    Update: fixed CnP bug in is_running. I never test posted code!

      OK, I'm just in awe right now that you could do that in a fraction of the time it took me to write the question!

      After I'm finished weeping because of how unworthy I am, I'm going to delve into that. Thank you.

      Incidentally, I've noticed that when I get stuck on something, the way out seems invariably to involve hashes. Unfortunately right now the part of my brain that may someday be really comfortable with hashes is really just a kind of empty hole. It's like when I first hear "hash" I go into a trance of some kind.

      "Lost time" I think they call it...

        I eventually filled that particular hole in my head with 'if I ever need to search it, use a hash'. Now I use an even simpler method. 'Always use hashes unless there's a really good reason to use a list'.

        Okay, people who know better, please throw fruit at me...

      I keep wanting to think of "running" as "1" and "not running" as "0"; Which is causing me to have trouble with sub is_running, which, if I read it correctly, returns "0" if running. Whereas sub init sets an initial widget state of "0" and sub set_running sets them to "1"; However sub replace sets "out" to "0" ("out" to me indicating "out of the pool" i.e. running) and "in" (as in, back in the garage so to speak, or not running anymore) to "0";

      So if is_running returns "0" if foo is running, I would do something like:

      if (is_running('foo')){ # because "1" means "no" set_running('foo'); }
      which seems so unnatural that I'm sure I must be missing the point somewhere.

        No, that was a coding error on my part. I copied the line from the sub above, and forgot to delete the  =0 part. Sorry; my bad. (Fixed now.)

Re: comparing array elements
by andye (Curate) on Mar 30, 2007 at 15:32 UTC
    One simple way: use a hash instead.
    my %running = ( foo => 1, bar => 1, baz => 1 ); if ($running{'foo'} == 1) { # do stuff } # foo has finished: delete $running('foo'}

    HTH!

    andye