http://www.perlmonks.org?node_id=404868

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

Hello!
I have an array of arrays built by parsing data from an XML file, which looks like this.
1-1-2004 33.95 2-1-2004 30.34 3-1-2004 31.50 4-1-2004 32.50 5-1-2004 33.50 . . . . . . . . .
Each row contains a date and a temperature value. I need to search the temperature values in this array of arrays.
For each consecutive 5 (for the time being, better to make this a variable)rows (days) where the temperature values
fall in the same range, I need to store in another array of arrays the start date, end date and a string describing the temperature range.

For example the array of arrays could look like:
1-1-2004 5-1-2004 hot 10-1-2004 15-1-2004 warm 25-1-2004 30-1-2004 mild ... ... ...
The temperature ranges I need to use are:
Over 40.0 extremelyhot 35.0 to 39.9 veryhot 30.0 to 34.9 hot 25.0 to 29.9 verywarm 20.0 to 24.9 warm 15.0 to 19.9 mild
Any help with coding this ?Any code or help would be appreciated.
Thanks
perl_seekr :)

2004-11-03 Edited by Arunbear: Changed title from 'Searching in an array of arraya.', as per Monastery guidelines

Replies are listed 'Best First'.
Re: Searching in an array of arrays
by zejames (Hermit) on Nov 03, 2004 at 12:09 UTC
    One way to do it (untested) :
    my $consecutive = 5; my ($last_range, $first_day); my %ranges = ( '15' => 'mild', '20' => 'warm', '25' => 'verywarm', '30' => 'hot', '35' => 'veryhot', '40' => 'extremelyhot'); foreach my $day (@data) { $day->[1] = int $day->[1]; my $range = $day->[1] - ($day->[1] % 5); if ($range == $last_range) { $seen++; } else { $last_range = $range; $first_day = $day->[0]; $seen = 1; } if ($seen == $consecutive) { print "$first_day $day->[0] " . $ranges{$range} . "\n"; } }
    BTW, a hash table would probably be more appropriate to store your tempature information.

    updated : corrected $last_range and $first_day, and make it word in Real world


    --
    zejames
      hm, at first sight i would say that $last_range, $first_day should be initialised outside the for-loop. look at and execute this:
      for ( 1 .. 10 ) { my $i; $i++; print $i; } print "\n"; my $i; for ( 1 .. 10 ) { $i++; print $i; } print "\n";
      Hello zejames!
      Thanks a lot for your reply. I do not understand your code fully, please bear with me. What
      are the variables last_range and first_day used for, and what should their initial values be?

      Also for this line,
      my $range = $day->[1] - ($day->[1] % 5);
      I get this error:
      Operation `%': no method found,left argument in overloaded package XML +::XPath::Literal,right argument has no overloaded magic at C:\..\xmlr +ecord_parse.pl line 604.
      Using sprintf removes the error, but I'm not sure whether this is correct(i.e. the control string "%f"):
      my $range = sprintf("%f","$day->[1]") - (sprintf("% f","$day->[1]") % 5);
      My array of arrays @data looks like this:
      [ 1-1-2004 15.0 ], [ 2-1-2004 15.5 ], [ 3-1-2004 16.5 ], [ 4-1-2004 17.0 ], [ 5-1-2004 17.5 ], [ 6-1-2004 18.0 ], [ 7-1-2004 18.5 ], [ 8-1-2004 19.0 ], [ 9-1-2004 19.5 ], [ 10-1-2004 25.0 ], [ 11-1-2004 26.5 ], [ 12-1-2004 27.5 ], [ 13-1-2004 28.0 ], [ 14-1-2004 29.5 ], [ 15-1-2004 28.0 ], [ 16-1-2004 28.8 ],
      So the resulting array of arrays I need to build should look like this:
      1-1-2004 5-1-2004 mild 10-1-2004 14-1-2004 verywarm
      The values of $range (printing inside the loop)I'm getting are:
      15 15.5 15.5 15 15.5 15 15.5 15 15.5 25 25.5 25.5 25 25.5 25 25.8
      This statements does not print anything:
      print "$first_day $day->[0] ".$ranges{$range}."\n";
      Please help, really need it!

      Thanks,
      perl_seeker:)
        I don't understand your first problem. Testing my code here with the @data defined as :
        @data = ( [ '1-1-2004', 15.0 ], [ '2-1-2004', 15.5 ], [ '3-1-2004', 16.5 ], [ '4-1-2004', 17.0 ], [ '5-1-2004', 17.5 ], [ '6-1-2004', 18.0 ], [ '7-1-2004', 18.5 ], [ '8-1-2004', 19.0 ], [ '9-1-2004', 19.5 ], ... [ '16-1-2004', 28.8 ]);
        does work here. Could you provide code ? Your problem seems to be XML related.

        As for the range, you're right, there is problem, that is corrected in the new version, by converting temperature to an integer :
        $day->[1] = int $day->[1];

        $last_range is used to remember the current range we are in. If that range remains unchanged 5 times, we print the range information.

        $first_day is used to remember the first day we saw the temperature of the current range;

        HTH

        --
        zejames
Re: Searching in an array of arrays
by trammell (Priest) on Nov 03, 2004 at 16:40 UTC
    This might not be helpful, since you may not have control over the input you receive, but I feel compelled to comment on your choice of date formats.

    I have found it useful to always use date format "YYYYMMDD", which has the following advantages:

    • numeric sort, lexical sort, and chrono sort are the same
    • ordering of units by size (years => months => days) are internally consistent, and consistent with our standard number representation
    • consistent with ISO standard date formatting
    Just a thought.
Re: Searching in an array of arrays
by TedPride (Priest) on Nov 04, 2004 at 08:41 UTC
    Here's a working version. I changed the temperature ranges slightly to take into account unlimited decimal places and the possibility of temperatures under 15.
    use strict; use warnings; my $rows = 5; my @temps = ('mild','warm','very warm','hot','very hot','extremely hot +'); my ($range, $lrange, $start, $c, @data, @results); # Creating nested arrays with data while (<DATA>) { chomp; push @data, [split(/ /)]; } $lrange = -1; for (@data) { $range = getrange(@$_[1]); if ($range == $lrange) { if (++$c == $rows) { push (@results, [$start, @$_[0], $temps[$range]]); $lrange = -1; $c = 0; } } else { $start = @$_[0]; $lrange = $range; $c = 1; } } # Printing results, tab delimited for (@results) { print join("\t", @$_) . "\n"; } sub getrange { return 5 if $_[0] >= 40; return 0 if $_[0] < 20; return int($_[0] / 5 - 3); } __DATA__ 1-1-2004 33.95 2-1-2004 30.34 3-1-2004 31.50 4-1-2004 32.50 5-1-2004 33.50 6-1-2004 90.00 7-1-2004 51.45 8-1-2004 52.56 9-1-2004 56.23 10-1-2004 50.54 11-1-2004 52.43
      Hi Ted! way to go, your code works just fine. I'll need to go through it properly to understand it fully. Your help is much appreciated.

      Cheers,
      perl_seeker:)
      Oops! I tested your code by reading the data from a text file to build @data, and it worked fine. While trying to
      run your code with the rest of my code( I have built @data by parsing an XML file using XML::XPath), I'm getting errors similar to this
      Operation `/': no method found, left argument in overloaded package XML::XPath::Literal,right argument has no overloaded magic at C:\..\xmlrecord_parse.pl line 608. Tool completed with exit code 255
      So I've changed this portion of code, but I'm not sure if this is correct :
      return 5 if (int($_[0]) >= 40); return 0 if (int($_[0]) < 20); return ((int($_[0]) / 5) - 3);
      However, the results printed now are:
      1-1-2004 5-1-2004 mild
      Whereas they should be:
      1-1-2004 5-1-2004 mild 10-1-2004 14-1-2004 very warm
      Data
      1-1-2004 15.0 2-1-2004 15.5 3-1-2004 16.5 4-1-2004 17.0 5-1-2004 17.5 6-1-2004 18.0 7-1-2004 18.5 8-1-2004 19.0 9-1-2004 19.5 10-1-2004 25.0 11-1-2004 26.5 12-1-2004 27.5 13-1-2004 28.0 14-1-2004 29.5 15-1-2004 28.0 16-1-2004 28.8
      What do I need to change in the code?
      Thanks,
      perl_seeker :)