1

I'm writing a script which reads data from a power meter. I've identified the wattage used by several devices and have a variable which identifies the wattage 'jump' when a device is turned on. I've declared each device wattage as a range and declared an array containing all device arrays as follows-

@device1 = (30..40);
@device2 = (50..70);
@device3 = (100..150);
@device_array1 = (\@device1, \@device2, \@device3);

The script reads the output from the meter and produces a $watts_jump variable. However I cant identify a working approach to match the variable against the @device_array1 and its 'sub' arrays.

Should I be doing a grep or be using List::Utils 'first' approach?

I've tried the following -

use List::Utils 'first';
my $device = first { /$watts_jump/ } @device_array1;

and as an alternative the grep for string method but neither find the match and report it back. If I print "$device_array1[1][2]\n"; or variation of - it does return the correct values.

If matched, I need what was matched and not its index e.g -

$watts_jump = 55 therefore $device = $device2

Any help or pointers to examples will be gratefully received. As you can probably tell this is my first attempt to do anything serious with perl and my first post here so be gentle. The first person to direct me to perldoc is off my xmas card list :-)

TIA

4
  • I'm not sure what information you're looking for. Assuming you've identified the correct array, what will your script then do with it? Commented Sep 27, 2015 at 0:37
  • I'm logging the overall wattage used to a DB and have a separate table for devices. So when I see a jump of say 1850 watts (a kettle), the script will recognise the jump and update the table with number of times the device was turned on and the total duration. Commented Sep 27, 2015 at 10:47
  • Would be interested to know how you're going to solve the problem when two devices have (roughly) the sama wattage. Commented Sep 27, 2015 at 16:46
  • If you have devices which fall within the same range its not possible to distinguish between them (e.g room lighting) so instead am grouping them together and increasing/decreasing a count dependant on the associated jump/drop. So I should be able to gauge the number of on's and off's and total durations. Its not bulletproof by any means but should be able to tune it to a certain degree of accuracy. Commented Sep 27, 2015 at 20:02

3 Answers 3

2

I'd do something like this:

my @specs = (
    { range =>  [30,  40], table => "foo" },
    { range =>  [50,  70], table => "bar" },
    { range => [100, 150], table => "baz" },
);

my $table;
for my $spec (@specs) {
    my ($lo, $hi) = @{$spec->{range}};
    if ($input >= $lo && $input <= $hi) {
        $table = $spec->{table};
        last;
    }
}

if (!defined $table) {
    die "Bad input: $input";
}

update_the_table($table);

That is, instead of pre-expanding the whole range and then testing list membership, I'd just store the endpoints and check if the input is between those.

I'd also store the name of the table you want to update in the same data structure because it keeps all relevant information in the same place.

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks Melpomene - I wasn't even aware I could take that approach. Is it more efficient/better practice than the 'grep' angle?
@RoyK I find it easier to understand. It's probably more efficient because it doesn't have to loop through a whole list for each device.
Apologies if this is the incorrect way to accept an answer but just wanted to thank you all for your help. I tried all three and all worked. At ikegami and rbm - yours worked and returned the index in the array but not the value associated. I probably didn't make that clear enough in my original request (or I've missed something in your replies). They did work though. @melpomene - yours did exactly what I needed the first time I tried it so thanks very much for that - I will building on it.
1

Cleaned up:

my @device1 = 30..40;
my @device2 = 50..70;
my @device3 = 100..150;
my @devices = ( \@device1, \@device2, \@device3 );

So you want to find the index into @devices, so you're going to iterate over the indexes of @devices. You can do this as follows:

my @matching_device_indexes = grep { ... } 0..$#devices;

The missing code should determine if the identified device matches the jump. The device to check is identified by index into @devices, which means the wattages to compare are in array referenced by $devices[$_]. As such, you can check if the identified device matches the jump as follows:

0+grep { $_ == $watts_jump } @{ $devices[$_] }

(The addition forces grep into scalar context, where it returns the number of matches rather than the matches themselves.)

All together:

my @matching_device_indexes =
   grep {
      0+grep { $_ == $watts_jump } @{ $devices[$_] }
   } 0..$#devices;

If you always deal with ranges, it makes more sense to just specify the endpoints of the ranges.

my @device1 = ( 30, 40 );
my @device2 = ( 50, 70 );
my @device3 = ( 100, 150 );
my @devices = ( \@device1, \@device2, \@device3 );

The solution becomes

my @matching_device_indexes =
   grep {
      $devices[$_][0] <= $watts_jump
         &&
      $watts_jump <= $devices[$_][1]
   } 0..$#devices;

3 Comments

Great - thanks ikegami - I'll give it a go later on today and come back to you.
Note that melpomene's solution assumes there's never going to be more than one match, but that seems very unlikely to me.
Note that the indexes returned by my solution are 0-based. Add 1 to make them 1-based.
0

A very simple solution with a hash:

use strict;
use warnings;

my @device1 = (30..40);
my @device2 = (50..70);
my @device3 = (100..150);
my $device_array1 = {1 => \@device1, 2 => \@device2, 3=> \@device3};
my $dev_count = scalar(keys %{$device_array1});

my $value = 65;

for (my $i=1; $i<=$dev_count; $i++)
{
    if ( scalar(grep $value == $_, @{$device_array1->{$i}}) > 0)
    {
       print "Device $i";
    }
}

4 Comments

Fails on various inputs, e.g. 33 or 0.
At that point, why use a regex at all? grep $_ eq $value, ... (or rather grep $_ == $value, ... - we are dealing with numbers, after all).
Thanks rbm - I'm going to try all of these options over the next day or so - thanks to you all for the suggestions - I will feedback.
Hi - I haven't forgotten about this...will come back to you

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.