Beefy Boxes and Bandwidth Generously Provided by pair Networks
Come for the quick hacks, stay for the epiphanies.
 
PerlMonks  

Tracking minimum of values in an array over time

by tj_thompson (Monk)
on Oct 05, 2011 at 18:45 UTC ( [id://929852]=perlquestion: print w/replies, xml ) Need Help??

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

Hello monks. I have a fairly simple algorithmic question. It seems to me there should be a good way to do this, but I haven't come up with anything yet and figured I'd toss it to you folks.

Assume I have an array with some arbitrary number of values in it all initialized to zero.

@a = (0, 0, 0, 0, 0);

After initialization, the values in this array are only incremented (never decremented). I never add or remove elements to or from the array.

# some time down the road after some number of increments @a = (3, 1, 8, 12, 2);

However, I am not really interested in the actual value of each element, but in the minimum value of the set of elements at any one time. Once a minimum floor value is reached, I'm ready to move on.

The array I'm dealing with will be very large with many increment operations. Is there a way to track the minimum value currently in the array without having to resort to a linear search through it after each increment?

Replies are listed 'Best First'.
Re: Tracking minimum of values in an array over time
by suaveant (Parson) on Oct 05, 2011 at 19:23 UTC
    Create a counter, at increment time check to see if the incremented value == the minimum value, when counter == @a start processing. This should work if you are updating through a single sub and if the minimum is constant. (if you need to be able to regenerate for some reason iterate once to set the count)

                    - Ant
                    - Some of my best work - (1 2 3)

      What ++suaveant said.

      I wrote the program below to illustrate how you might do this. Note that you increment your "check" variable (in my program it's called $nfloor) only when an element becomes exactly the "floor" value. You can change the value of $b_slow to zero to make it call the subroutine fast() instead of slow(), and play with the values of $array_size and $floor to see how they affect the overall program speed. ($b_debug, if set, prints out the array each time; you probably only want to do this if $array_size and $floor are both quite small!):

      #!/usr/bin/perl -w # Libraries use strict; use warnings; use 5.010; # User-defined my $b_slow = 1; my $array_size = 3000; my $floor = 50; my @array = ( 0 ) x $array_size; my $b_debug = 0; # Main program my $start = time(); while (1) { # Pick a random index, and increment the corrsponding value my $idx = int(rand(@array)); # Check all values to see if they've reached $floor my $b_reached = $b_slow? slow(\@array, $idx): fast(\@array, $idx); $b_debug and print "@array\n"; $b_reached and last; } my $nsecs = time() - $start; print "Reached floor '$floor' in $nsecs second(s)\n"; # Subroutines sub slow { my ($a_array, $idx) = @_; ++$array[$idx]; for (my $i = 0; $i < @$a_array; $i++) { my $val = $a_array->[$i]; ($val < $floor) and return 0; } return 1; } sub fast { my ($a_array, $idx) = @_; state $nfloor = 0; my $val = ++$a_array->[$idx]; if ($floor == $val) { ++$nfloor; } return ($nfloor < $array_size)? 0: 1; }

      s''(q.S:$/9=(T1';s;(..)(..);$..=substr+crypt($1,$2),2,3;eg;print$..$/

        Now that's a fantastic idea. Instead of trying to track the minimum of the set, track how many in the set have passed my floor value.

        My increment value will usually be more than one, so I'll have to verify that the increment did result in crossing the floor, but that's easy enough.

        Great idea from both of you, many thanks!

        liverpole,
        I haven't read your code but in thinking about the problem, you should never need to scan the array. The following is completely untested but it explains why I believe it is unnecessary.
        my @array = ((0) x 100); my $inc_array = gen_min_tracker(0, 100, \@array); my $floor = 27; my $new_min = inc_array(42, 7); # increment the 43rd element by 7 if ($new_min >= $floor) { print "The floor has been reached\n"; } sub gen_min_tracker { my ($curr_min, $curr_cnt, $aref) = @_; my ($next_min, $next_cnt); return sub { my ($idx, $inc) = @_; my $val = $aref->[$idx]; my $add = $val + $inc; if ($val == $curr_min) { $curr_cnt--; if (! defined $next_min || $add <= $next_min) { $next_min = $add; $next_cnt++; } if (! $curr_cnt) { ($curr_val, $curr_cnt, $next_val, $next_cnt) = ($next_ +val, $next_cnt, undef, undef); } } $aref->[$idx] += $inc; # let's not forget to actually increme +nt return $curr_min; }; }
        Update: Due to some unknown copy/paste error, the code above has been altered slightly from its original form to hopefully be correct despite still not being tested.

        Warning: In the CB, ambrus points out that this approach is flawed. It is not safe to reset $next_val and $next_cnt to undef. The assumption was that anytime one of the $curr_min values was incremented, it would be incremented to the $next_min and that no other value in the array could possibly be smaller. The solution is simple - put in the O(N) low water mark algorithm keeping track of the min value and count.

        Consideration: While you can't keep track of the minimum value without periodically scanning, you can still achieve the OP's goal as suaveant points out.

        Cheers - L~R

Re: Tracking minimum of values in an array over time
by davido (Cardinal) on Oct 05, 2011 at 19:05 UTC

    If it's impractical to keep track of all the possible ways elements may be incremented (which may be indicative of a design flaw), perhaps tieing it to a class that keeps track of updates behind the scenes through an accumulator would work for (or rescue) you. By taking this approach, and keeping track of updates as they happen, you trade O(n) tests for O(1) tests. The tradeoff is that a tied array itself carries with it more computational overhead than a simple array.

    The class would need one accessor for the minimum value, so when your array is tied you would need to keep a copy of the object reference so that $o->get_min(); would work.

    Be aware that you will have to guard against The untie Gotcha if you are using both the tied variable's primary interface (the array) and its object interface( $o->whatever() ).


    Dave

Re: Tracking minimum of values in an array over time
by keszler (Priest) on Oct 05, 2011 at 19:07 UTC

    List::Util has a min function, and being XS-based it's quick.

Re: Tracking minimum of values in an array over time
by ambrus (Abbot) on Oct 06, 2011 at 15:47 UTC

    You can use a priority queue, such as a binary heap (Not necessarily that implementation, just something similar.) You keep the indices and values of your array in this priority queue, with lowest values down. Whenever you change a number in the array, you delete the corresponding entry from the priority queue and then reinsert a new one. The priority queue will then keep track of the element with the lowest value for you.

      A min heap would be an excellent way to determine the min value of the set at any time. Good call. For my particular problem, since I'm actually interested in making certain that all elements have crossed the floor value (as pointed out by Ant above), I think counting the number of values that cross the floor as they are incremented is the smartest solution.

      Good thoughts Ambrus, thanks!

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://929852]
Approved by davido
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others wandering the Monastery: (5)
As of 2024-04-23 17:22 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found