So, i have a few variables that i need to check for emptyness, and it would be nicer and easier if i could do it in some way other than comparing each and every one like this
if ($var1 ne "" && $var2 ne "" && $var3 ne"")
So is there a better way?
PS: If anybody is going to propose to put them in a hash and loop through them, they are already in a hash, but they're not alone there, i have a lot of other values there(all are needed together later in the code, so spliting it wouldn't be easy).
You could make use of List::Util::all like this
use List::Util 'all';
if ( all { $_ ne '' } $var1, $var2, $var3 ) { ... }
but I would expect to see it in the form you show
If you are testing the values of a hash then you should use a hash slice to select them
if ( all { $_ ne '' } #hash{#keys_to_check} ) { ... }
If they're in a hash, you can access them as a slice and grep them:
Something like this:
#!/usr/local/bin/perl
use strict;
use warnings;
my %hash = (
"to_test" => "",
"to_ignore" => "some value",
"also_to_test" => "",
"not_to_test" => "content here",
);
my #keys = qw ( to_test also_to_test );
unless ( grep { $_ ne '' } #hash{#keys} ) {
print "Keys are all empty: #keys\n";
}
You could do a similar thing with named variables, but that's perhaps not quite as elegant.
You can use grep with inverted logic,
if (!grep {$_ eq ""} $var1, $var2, $var3) { .. }
If there are too many variables, you can put their scalar references into an array, and perform grep on it,
my #sref = \($var1, $var2, $var3);
if (!grep {${$_} eq ""} #sref) { .. }
${$_} or $$_ dereferences scalar reference.
If those are in hash, then create an array with the keys you want to test and loop through them checking if value in hash is empty.
Related
I have a scalar that may or may not be a reference to an array. If it is a reference to an array, I would like to dereference it and iterate over it. If not, I would like to treat it as a one-element array and iterate over that.
my $result = my_complicated_expression;
for my $value (ref($result) eq 'ARRAY' ? #$result : ($result)) {
# Do work with $value
}
Currently, I have the above code, which works fine but feels clunky and not very Perlish. Is there a more concise way to express the idea of dereferencing a value with fallback behavior if the value is not what I expect?
Just force it before the loop.
Limited, known ref type
my $result = *some function call* // [];
$result = [$result] if ref $result ne 'ARRAY';
for my $val ( #$result ){
print $val;
}
Ref type unknown
#!/usr/bin/perl
use 5.012;
use strict;
no warnings;
sub array_ref;
my $result = [qw/foo bar foobar/];
# $result = 'foo'; # scalar test case
# $result = {foo=>q{bar}}; # hash test case
$result = array_ref $result;
for my $val ( #$result ){
say $val;
}
sub array_ref {
my $ref = shift;
given(ref $ref){
$ref = [%$ref] when('HASH');
$ref = [$ref] when(['SCALAR','']);
when('ARRAY'){}
default {
die 'Did not prepare for other ref types';
}
}
return $ref;
}
This is for demo purposes (you shouldn't use given/when in production code), but shows you could easily test for the ref type and cast a new response. However, if you really don't know what type of variable your function is returning, how are you sure it's even a reference. What if it was an array or hash?
Being perl, there's going to be several answers to this with the 'right' one being a matter of taste - IMHO, an acceptable shortening involves relying on the fact that the ref function returns the empty string if the expression given it is scalar. This means you don't need the eq 'ARRAY' if you know there are only two possibilities (ie, a scalar value and an array ref).
Secondly, you can iterate over a single scalar value (producing 1 iteration, obviously), so you don't have to put the $result in parentheses in the "scalar" case.
Putting these two small simplifications togeather gives;
use v5.12;
my $result1 = "Hello World";
my $result2 = [ "Hello" , "World" ];
for my $result ($result1, $result2) {
for my $value ( ref $result ? #$result : $result) {
say $value ;
}
}
which produces;
Hello World
Hello
World
There's likely to be 'fancier' things you can do, but this seems a reasonable compromise between being terse and readable. Of course, YMMV.
I see that I'm late to this, but I can't help it. With eval and $#, and the comma operator
my $ra = [ qw(a b c) ];
my $x = 23;
my $var = $ra;
# my $var = $x; # swap comment to test the other
foreach my $el ( eval { #{$var} }, $# && $var )
{
next if $el =~ /^$/; # when #$var is good comma adds empty line
print $el, "\n";
}
Prints a b c (one per line), if we swap to my $var = $x it prints 23.
When $var has the reference, the $# is empty but the comma is still executed and this adds an empty line, thus the next in the loop. Alternatively to skipping empty lines one can filter them out
foreach my $el ( grep { !/^$/ } eval { #{$var} }, $# && $var )
This does, in addition, clean out empty lines. However, most of the time that is desirable.
sub deref {
map ref($_) eq 'ARRAY'? #$_ : ref($_) eq 'HASH'? %$_ : $_, #_
}
sub myCompExpr {
1, 2, 3, [4, 5, 6], {Hello => 'world', Answer => 42}
}
print $_ for deref myCompExpr
Here's my code:
my %hash = (
'2564' => {
'st_responsible' => 'mname1',
'critical' => '',
'last_modified_by' => 'teamname1',
'transstatus' => '',
'rt_res' => 'pname1'
},
'2487' => {
'st_responsible' => 'mname2',
'critical' => '',
'last_modified_by' => 'teamname2',
'transstatus' => '',
'rt_res' => ''
}
);
print "xnum,st_responsible,critical,last_modified_by,transstatus,rt_res\n";
foreach my $x_number (sort keys %hash)
{
print "$x_number";
foreach my $element (keys %{$hash{$x_number}})
{
print ",$hash{$x_number}{$element}";
}
print "\n";
}
Expected output
xnum,st_responsible,critical,last_modified_by,transstatus,rt_res
2487,mname2,,teamname2,,
2564,mname1,,teamname1,,pname1
Actual output
xnum,st_responsible,critical,last_modified_by,transstatus,rt_res
2487,mname2,,,teamname2,
2564,mname1,,,teamname1,pname1
Please help in letting me know as to how exactly do I preserve the order of this data structure, and then write this to a CSV file.
I would suggest that for this, you'd be better off doing this with a slice, which is a way of extracting a list of values from a hash in a particular order?
#configure field order
my #order = qw ( st_responsible critical last_modified_by transstatus rt_res );
#print header row
print join (",", "xnum", #order ),"\n";
#iterate the rows
foreach my $key ( sort keys %hash ) {
#extract hash slice and join it with commas
print join ( ",", $key, #{$hash{$key}}{#order} ),"\n";
}
This gives:
xnum,st_responsible,critical,last_modified_by,transstatus,rt_res
2487,mname2,,teamname2,,
2564,mname1,,teamname1,,pname1
You can consider Text::CSV - but I'd suggest in this scenario it's overkill, best used when you've got quotes and quoted field separators to worry about. (And you don't).
If you have to deal with not just empty keys, but missing ones, you can make use of map:
my #order = qw ( st_responsible critical last_modified_by
transstatus missing rt_res extra_field_here );
print join (",", "xnum", #order ),"\n";
foreach my $key ( sort keys %hash ) {
print join ( ",", $key, map { $_ // '' } #{$hash{$key}}{#order} ),"\n";
}
(Otherwise you'll get a warning about an undef value).
Perl doesn't guarantee the order of items in the hash, this is the root cause of the issue. Even two different hashes with the same keys can have different order of keys. It may also differ from platform to platform and architecture and perl version.
You need to define another array with the list of keys which you want to print in correct order.
my #keys = qw(st_responsible critical last_modified_by transstatus rt_res);
foreach my $element (#keys) {
... print the value
}
EDIT: As you're trying to write CSV file, consider using Text::CSV which takes care about special characters, correct formatting and things like that.
There's probably a slicker way of achieving this, but give this a go:
use warnings;
use strict;
open my $csv_out, '>', 'out.csv' or die $!;
my #keys = qw(2487 2564);
my #vals = qw(st_responsible critical last_modified_by transstatus rt_res);
print $csv_out "xnum,st_responsible,critical,last_modified_by,transstatus,rt_res\n";
for my $key (#keys){
print $csv_out "$key,";
for my $vals (#vals){
$vals eq $vals[-1] ? print $csv_out "$hash{$key}{$vals}\n" : print $csv_out "$hash{$key}{$vals},";
}
}
This will print out comma-separated values to a csv file out.csv maintaining your original order (by iterating over arrays). If it's the last value it will print a newline.
--- OUTPUT ---
xnum,st_responsible,critical,last_modified_by,transstatus,rt_res
2487,mname2,,teamname2,,
2564,mname1,,teamname1,,pname1
I have an array with two values and need to perform some operations if input is not in that array.
I tried like
if ($a ne ('value1' || 'value2')
if (($a ne 'value1' ) || ($a ne 'value2' ))
Both methods didn't work. Can anyone please help?
You could use the none function from List::MoreUtils.
If you really have an array as your subject line says then your code would look like this
use List::MoreUtils 'none';
if ( none { $_ eq $a } #array ) {
# Do stuff
}
or if you really have two constants then you could use this
if ( none { $_ eq $a } 'value1', 'value2' ) {
# Do stuff
}
but in this case I would prefer to see just
if ( $a ne 'value1' and $a ne 'value2' ) {
# Do stuff
}
$a is not in the array if it's different to the first element and it's different to the second one, too.
if ($x ne 'value1' and $x ne 'value2') {
For a real array of any size:
if (not grep $_ eq $x, #array) {
(I use $x instead of $a, as $a is special - see perlvar.)
if ($a ne ('value1' || 'value2')
evaluates to
if ($a ne 'value1')
and
if (($a ne 'value1' ) || ($a ne 'value2' ))
is always TRUE.
You might try
if ($a ne 'value1' and $a ne 'value2')
or
if (!grep{$a eq $_} 'value1', 'value2')
Building on the smartmatch solution by #Dilbertino (nice nick) using match::simple by #tobyink to ease the pain of smartmatch going away (I miss it already):
use match::simple;
my #array = qw(abcd.txt abcdeff.txt abcdweff.txt abcdefrgt.txt);
my $x="abcd.txt" ;
say "it's there" if ($x |M| \#array );
The |M| operator from match::simple can be replaced with a match function which speeds things up a bit (it is implemented with XS):
use match::simple qw(match);
my #array = qw(abcd.txt abcdeff.txt abcdweff.txt abcdefrgt.txt);
my $x="xyz.txt" ;
if ( match ( $x, \#array ) ) {
say "it's there!" ;
}
else {
say "no hay nada";
}
It's "simple" because the RHS controls the behavior. With match::simple if you are matching against an array on the RHS it should be an arrayref.
Smart::Match also has a none function. To use it you would do:
if ( $x ~~ none (#array) ) {
say "not here so do stuff ...";
}
Appendix
Discussion here on Stackoverlfow (see: Perl 5.20 and the fate of smart matching and given-when?) and elsewhere (c.f. the Perlmonks article by #ikegami from circa perl-5.18) gives the context for the smartmatch experiment. TLDR; things might change in the future but meanwhile, you can go back in time and use match::smart qw(match); with perl-5.8.9 proving once again that perl never dies; it just returns to its ecosystem.
In the future something like Smart::Match (i.e. the non-core CPAN module not the concept) can help supercharge a simplified smart matching operator with helper functions that read like adverbs and adjectives and have the added bonus (as I understand it) of clarifying/simplifying things for perl itself since the ~~ operator will have a less ambiguous context for its operations.
I would do something like this using grep with a regex match
#!/usr/bin/perl
use warnings;
use strict;
my #array = ('value1','value2');
if(grep(/\bvalue1\b|\bvalue2\b/, #array)){
print "Not Found\n";
}
else {
print "do something\n";
}
You can also use the smart match operator:
unless( $x ~~ ['value1','value2'] )
Your variable $a is not evaluated as a array without [INDEX] index, but is been treated as a scalar.
Two value array:
$array[0] = "X";
$array[1] = "Y";
or
#array = qw/X Y/;
Condition check using if:
if ( $array[0] ne "Your-String" || $array[1] ne "Your-String")
I have a Perl script where variables must be initialized before the script can proceed. A lengthy if statement where I check each variable is the obvious choice. But maybe there is a more elegant or concise way to check several variables.
Edit:
I don't need to check for "defined", they are always defined with an empty string, I need to check that all are non-empty.
Example:
my ($a, $b, $c) = ("", "", "");
# If-clauses for setting the variables here
if( !$a || !$b || !$c) {
print "Init failed\n";
}
I am assuming that empty means the empty string, not just any false value. That is, if 0 or "0" are ever valid values post-initialization, the currently accepted answer will give you the wrong result:
use strict; use warnings;
my ($x, $y, $z) = ('0') x 3;
# my ($x, $y, $z) = ('') x 3;
for my $var ($x, $y, $z) {
die "Not properly initialized\n" unless defined($var) and length $var;
}
Now, this is pretty useless as a validation, because, more than likely, you would like to know which variable was not properly initialized if this situation occurs.
You would be better served by keeping your configuration parameters in a hash so you can easily check which ones were properly initialized.
use strict; use warnings;
my %params = (
x => 0,
y => '',
z => undef,
);
while ( my ($k, $v) = each %params ) {
validate_nonempty($v)
or die "'$k' was not properly initialized\n";
}
sub validate_nonempty {
my ($v) = #_;
defined($v) and length $v;
}
Or, if you want to list all that were not properly initialized:
my #invalid = grep is_not_initialized($params{$_}), keys %params;
die "Not properly initialized: #invalid\n" if #invalid;
sub is_not_initialized {
my ($v) = #_;
not ( defined($v) and length $v );
}
use List::MoreUtils 'all';
say 'Yes' if (all { defined } $var1, $var2, $var3);
What do you mean by "initialized"? Have values that are not "undef"?
For a small amount of values, the straightforward if check is IMHO the most readable/maintainable.
if (!$var1 || !$var2 || !$var3) {
print "ERROR: Some are not defined!";
}
By the way, checking !$var is a possible bug in that "0" is false in Perl and thus a string initialized to "0" would fail this check. It's a lot better to use $var eq ""
Or better yet, space things out for >3 values
if (!$var1 # Use this if your values are guarantee not to be "0"
|| $var2 eq "" # This is a LOT better since !$var fails on "0" value
|| $var3 eq "") {
print "ERROR: Some are not defined!";
}
If there are so many values to check that the above becomes hard to read (though with per-line check as in the second example, it doesn't really ever happen), or if the values are stored in an array, you can use grep to abstract away the checking:
# We use "length" check instead of "$_ eq ''" as per tchrist's comment below
if (grep { length } ($var1, $var2, $var3, $var4, $var5, #more_args) ) {
print "ERROR: Some are not defined!";
}
If you must know WHICH of the values are not defined, you can use for loop (left as an obvious excercise for the reader), or a map trick:
my $i = -1; # we will be pre-incrementing
if (my #undefined_indexes = map { $i++; $_ ? () : $i }
($var1, $var2, $var3, $var4, $var5, #more_args) ) {
print "ERROR: Value # $_ not defined!\n" foreach #undefined_indexes;
}
use List::Util 'first';
if (defined first { $_ ne "" } $a, $b, $c) {
warn "empty";
}
Your way is readable and easy to understand which means it's easy to maintain. Restating your boolean using de Morgan's laws:
if (not($a and $b and $c)) {
warn(qq(Not all variables are initialized!))
}
That way, you're not prefixing not in front of every variable, and it doesn't affect readability. You can use List::Util or List::MoreUtils, but they don't really add to the legibility.
As Sinan Ünür stated, if you put the variables in a hash, you could parse through the hash and then list which variables weren't initialized. This might be best if there are a lot of these variables, and the list keeps changing.
foreach my $variable qw(a b c d e f g h i j) {
if (not $param{$variable}) {
warn qq(You didn't define $variable\n);
}
}
You can use Getopts::Long to put your parameter values inside a hash instead of separate variables. Plus, the latest versions of Getopts::Long can now operate on any array and not just #ARGV.
It seems like I should be able to do this with map, but the actual details elude me.
I have a list of strings in an array, and either zero or one of them may have a hash value.
So instead of doing:
foreach $str ( #strings ) {
$val = $hash{$str} if $hash{$str};
}
Can this be replaced with a one-liner using map?
#values = grep { $_ } #hash{#strings};
to account for the fact that you only want true values.
Change this to
#values = grep { defined } #hash{#strings};
if you want to skip undefined values.
Sure, it'd be:
map { $val = $hash{$_} } #strings;
That is, each value of #strings is set in $_ in turn (instead of $str as in your foreach).
Of course, this doesn't do much, since you're not doing anything with the value of $val in your loop, and we aren't capturing the list returned by map.
If you're just trying to generate a list of values, that'd be:
#values = map { $hash{$_} } #strings;
But it's more concise to use a hash slice:
#values = #hash{#strings};
EDIT: As pointed out in the comments, if it's possible that #strings contains values that aren't keys in your hash, then #values will get undefs in those positions. If that's not what you want, see Hynek's answer for a solution.
I'm used to do it in this way:
#values = map { exists $hash{$_} ? $hash{$_} : () } #strings;
but I don't see anything wrong in this way
push #values, $hash{$_} for grep exists $hash{$_}, #strings;
or
#values = #hash{grep exists $hash{$_}, #strings};
map { defined $hash{$_} && ( $val = $hash{$_})} #strings;