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
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.
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With