Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Comparing and validating data structures

Tags:

perl

I have to check hashrefs like this one

{ foo => 65, bar => 20, baz => 15 }

against an arrayref of hashrefs expressing conditions like this

[
 { foo => { "<=" => 75 } },
 { bar => { "==" => 20 } },
 { baz => { ">=" => 5 } },
]

and return a true value if all conditions are fulfilled.

Neither of the two data structures is pre-determined. One is built from parsing a string in a database, the other from parsing user input.

In the case above, I would return true, but if I checked the hashref against

[
 { foo => { "<=" => 60 } },
 { bar => { "==" => 20 } },
 { baz => { ">=" => 5 } },
]

I would return false, because foo in the first hashref is not <= 60.

The question is: what's the best strategy for doing that?

I am thinking of

  • building a series of subrefs via eval
  • checking against the appropriate one among 5 different pre-built subrefs (one per case for >, <, <=, >= and ==)

Am I going down the wrong path altogether? and if not, what's the best, eval or pre-built functions?

I have looked into Params::Validate but I am concerned that it'd be a lot of overhead, and I'd have to build the callbacks anyway.

like image 346
simone Avatar asked Mar 04 '12 10:03

simone


Video Answer


2 Answers

Use code references instead, and you will have ready to go validators. I simplified your condition-structure. There is no need to have an extra array level in there, unless you have duplicate hash keys, which I assume you don't.

The simplistic sub { $_[0] <= 75 } will simply compare the first value of the arguments. By default, the last value evaluated in the subroutine will be its return value.

use v5.10;
use strict;
use warnings;

my $in = { foo => 65, bar => 21, baz => 15 };

my $ref = {
    foo => sub { $_[0] <= 75 } ,
    bar => sub { $_[0] == 20 } ,
    baz => sub { $_[0] >= 5 } ,
};

for my $key (keys %$in) {
    if ($ref->{$key}($in->{$key})) {
        say "$key : Valid";
    } else {
        say "$key : Invalid";
    }
}

Output:

bar : Invalid
baz : Valid
foo : Valid
like image 190
TLP Avatar answered Sep 30 '22 07:09

TLP


To build on TLP's answer, you can also easily create the anonymous subs from your existing array-of-hashes:

my $array_of_hashes = [
    { foo => { "<=" => 75 } },
    { bar => { "==" => 20 } },
    { baz => { ">=" => 5 } },
];

my $ref = {};
foreach my $entry ( @$array_of_hashes ) {
    my ($key, $subhash) = %$entry;
    my ($op, $num) = %$subhash;
    $ref->{$key} = {
        '<=' => sub { $_[0] <= $num },
        '==' => sub { $_[0] == $num },
        '>=' => sub { $_[0] >= $num },
    }->{$op};
}

This assumes that you only have one check for each field in your original array-of-hashes. If you might have several, things get a bit more tricky, but you could always do something like this:

my $ref = {};
foreach my $entry ( @$array_of_hashes ) {
    my ($key, $subhash) = %$entry;
    my ($op, $num) = %$subhash;
    my $chain = $ref->{$key} || sub {1};
    $ref->{$key} = {
        '<=' => sub { $_[0] <= $num and $chain->($_[0]) },
        '==' => sub { $_[0] == $num and $chain->($_[0]) },
        '>=' => sub { $_[0] >= $num and $chain->($_[0]) },
    }->{$op} || $chain;
}

Ps. In case anyone's wondering just how this code can possibly work, the answer is: closures. Specifically, when those anonymous subs are created inside the loop, they retain references to the lexical variables $num and $chain, even after these variables go out of scope at the end of the current iteration of the loop. So, forever thereafter, those variables will be safely squirreled away, accessible only from the subroutine we've created.

like image 28
Ilmari Karonen Avatar answered Sep 30 '22 08:09

Ilmari Karonen