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

I have an array of hash, where one of the hash keys is date and the array is sorted on date. I want to set "isnext" in the appropriate hash based on todays date.

I can think of some brute force methods, but am looking for a more elegant solution that doesn't involve multiple trips thru the array, or saving "previous" values.

$today = '2001-03-28'; $array = [{date => '2001-02-01', more } ,{date => '2001-03-31', " } # <- want this one marked "isnex +t" ,{date => '2001-04-15', " }];
given above would return reference to second array element (don't want to be destructive to array), or perhaps set "isnext => 1" on the desired hash.

Replies are listed 'Best First'.
Re: Finding array element
by Masem (Monsignor) on Mar 28, 2001 at 22:57 UTC
    Let's start off with the easy part, identifying the location of said element:
    my $i = 0; foreach my $href ( @array ) { last if date_larger_than( $href->{ 'date' }, $today ); $i++ } # Mark it, if you want, assuming found... if ( $i < scalar @array ) { # $array[$i]->{ 'isnext' } = 1; }
    That's the easy part. Now, you need to define date_larger_than, which I can think of several possibilities.

    If you don't need the textual representation of the data; that is, if you can handle "20010201" as well as "2001-02-01", then you can simply use a numerical comparison, assuming that the hash's dates are changed to the numerical representation as well.

    You can change them on the fly, as well:

    my ($y, $m, $d) = ( $date =~ /(\d{4})-(\d{2})-(\d{2})/ ); $date_num = $y*10000 + $m*100 + $d;
    And compare like that.

    There's numerous date classes that could probably read that date in into an object, and then use their built in date comparators to see which date is greater.

    In this case , for date comparisons, there is definitely MTOWTDI.

    Update - Added grouping on the regex above, oops


    Dr. Michael K. Neylon - mneylon-pm@masemware.com || "You've left the lens cap of your mind on again, Pinky" - The Brain
      There's no need for a regex here, and the math is doubly overkill. Let's use my favorite underrated function, the transliteration operator:
      my $date = "2001-03-28"; (my $d2 = $date) =~ tr/-//d;
      If the date is in YYYY-MM-DD format, then a simple string comparison works fine
      my @dates = qw( 2001-12-24 1999-03-2 2004-04-23 ); print join(', ', sort { $b cmp $a } @dates );
      prints
      2004-04-23, 2001-12-24, 1999-03-2
      The whole thing is much simpler if you use grep. In fact, you can find, and set the 'isnext' key in a single line. What you want to find is the smallest date larger than or equal to the given date. First sort the dates in ascending order:
      sort { $a->{date} cmp $b->{date} } @$array
      then grep out those that are larger than the given date:
      grep { $_->{date} ge $today } ...
      and the first one of those will be your match:
      ( ... )[0]->{isnext} = 1;
      So the answer goes like this:
      (grep {$_->{date} ge $today} sort {$a->{date}cmp $b->{date} } @$array) +[0]->{isnext} = 1;
      The whole thing then looks like this:
      my $today = '2001-03-28'; my $array = [ { date => '2001-02-01' }, { date => '1999-04-09' }, { date => '2001-03-31' }, { date => '2001-03-24' }, { date => '2001-04-15' }, ]; # find the smallest date larger than or equal to today. # set the isnext flag (grep { $_->{date} ge $today } sort { $a->{date} cmp $b->{date} } @$a +rray)[0]->{isnext} = 1; # print all out and indicate which one is next... for my $e (@$array) { print "\n", $e->{date}; print " this is next" if $e->{isnext}; } # alternatively : my $thenext = (grep { $_->{date} ge $today } sort { $a->{date} cmp $b- +>{date} } @$array)[0]; print "\nThe next date is: $thenext->{date}\n";
      Thanks. I'm set now.

      FWIW I am using HTTP::Date::parse_date to get dates into a string format that is suitable for sorting.

Re: Finding array element
by sachmet (Scribe) on Mar 28, 2001 at 23:22 UTC
    Date::Calc is great for this sort of thing:
    #!/usr/bin/perl -w use strict; use Date::Calc qw(Delta_Days); my $curdiff = 1e10; my $nextrow = 0; my $array = [ { date => '2001-02-01', more => 1 }, { date => '2001-03-31' }, { date => '2001-04-15' } ]; my $today = '2001-03-28'; my ($nowy,$nowm,$nowd) = split(/-/,$today); foreach (my $rownum = 0; $rownum < scalar(@{$array}); $rownum++) { my ($nexty,$nextm,$nextd) = split(/\-/,${$array->[$rownum]}{'date'}) +; my $delta = Delta_Days($nowy,$nowm,$nowd,$nexty,$nextm,$nextd); next if $delta < 1; if ($delta < $curdiff) { $nextrow = $rownum; $curdiff = $delta; } } # for insert mark $array->[$nextrow]->{'isnext'} = 1; # for array ref my $arrayref = $array->[$nextrow];
    Of course, this could be optimized more fully, but it should be enough to get you started.