Perl splitting array based on condition using grep - arrays

I have some perl code that looks something like this:
my #array = map { rand } ( 1..100 );
my #matching = grep { $_ == $condition } #array;
#array = grep { $_ != $condition } #array;
This works ok, but what I would like to do is split the original array into two based on a single operation...I think I'm carrying out twice as many operations as strictly necessary.
Help appreciated!! Thanks.

This is where part from List::MoreUtils comes in handy.
use List::MoreUtils qw'part';
my($even,$odd) = part { $_ % 2 } #array;
This works great if you want each element of input in exactly one array of the output.
If you want to possibly put them in more than one of the arrays, you have to loop over them yourself.
The best way to do that is with a foreach loop.
my(#div2,#div3);
for my $elem (#array){
push #div2, $elem unless $elem % 2;
push #div3, $elem unless $elem % 3;
}
If there are a lot of similar checks you have to do, perhaps you should loop on what your testing against as-well.
my %div;
for my $elem (#array){
for my $div (2,3,5,7,11,13){
push #{ $out{$div} }, $elem unless $elem % $div;
}
}

By far the easiest method is to iterate your array and push values to either of the two arrays depending on the condition, as in the below example.
for (#array) {
if ($_ % 2) {push #odd, $_}
else {push #even, $_}
}
If you'd like to modify the source array:
for (my $i =0; $i < #array; ++$i) {
if ($array[$i] % 2) {
push #odd, splice (#array, $i--, 1);
}
}
Why didn't you recommend List::MoreUtils::part?
The module in question might not exists on the target system, which is always an annoying thing.
Also on the system I ran tests on I found that List::MoreUtils::part was twice as slow as first snippet in this post, though with different implementations of part it might be the opposite actually.

I love the simplicity of List::MoreUtils' part function:
sub part (&#) {
my ($code, #list) = #_;
my #parts;
push #{ $parts[ $code->($_) ] }, $_ foreach #list;
return #parts;
}
The resulting #parts array is an array of arrayrefs. #$parts[0] is the array of elements that returned false. #$parts[1] returned true.

Related

How does foreach(#array, $var) work in Perl?

I am trying to figure out some Perl code someone else wrote, and I'm confused with the following syntax of foreach loop
foreach (#array, $var){
....
}
This code runs, but nobody else uses it based on my google search online. And it doesn't work the same way as the more common way of foreach with arrays, which is:
foreach $var (#array){
....
}
Could someone explain this syntax?
foreach (#array, $var){ .... }
is short for
foreach $_ (#array, $var){ .... }
which is short for
foreach $_ ($array[0], $array[1], ... $array[N], $var){ .... }
So on each iteration of the loop, $_ is set to be each element of the array and then finally to be $var. The $_ variable is just a normal perl variable, but one which is used in many places within the Perl syntax as a default variable to use if one is not explicitly mentioned.
Perl's for() doesn't operate on an array, it operates on a list.
for (#array, $var) {...}
...expands #array and $var into a single list of individual scalar elements, and then iterates over that list one element at a time.
It's no different than if you had of done:
my #array = (1, 2, 3);
my $var = 4;
push #array, $var;
for (#array) {...}
Also, for my $var (#array, $var) {...} will work just fine, because with my, you're localizing a new $var scalar, and the external, pre-defined one is left untouched. perl knows the difference.
You should always use strict;, and declare your variables with my.

Access the key value from an associative array

I have the associative array %cart_item, within this is a series of associative arrays. I need to access the value of the keys within %cart_item. I have the following code which iterates on each array key. (I do the equivalent of php's continue if the value is 'meta')
my $key_value;
for (keys %cart_item) {
next if (/^meta$/ || /^\s*$/);
}
I need to do something like this though (although this isn't valid), setting the value of the keys in the loop:
my $key_value;
for $i (keys %cart_item) {
next if (/^meta$/ || /^\s*$/);
$key_value = $i;
# do stuff
}
Could anyone suggest a solution here? Apologies if this is obvious, I'm a Perl newbie. Thanks
I think you are asking for
for my $key (keys %cart_item) {
next if $key =~ /^meta$/ || $key =~ /^\s*$/;
my $val = $cart_item{$key};
...
}
If you're just looking for the value that goes with the key, you can get both at the same time with each:
while (my ($key, $val) = each %cart_item) {
next if $key eq 'meta' || $key =~ /^\s*$/;
...
}
That's the equivalent of PHP's foreach ($cart_item as $key => $val).
I also changed the "meta" check to use simple string equality; no need to use a regular expression for an exact match.
Your original code has
for ( keys %cart_item ) {
next if (/^meta$/ || /^\s*$/);
}
which works fine because the for has no loop control variable so it defaults to Perl's "pronoun" it variable $_. In addition, your regex pattern matches have no object so they also default to $_
Written fully, this would be
for $_ ( keys %cart_item ) {
next if ( $_ =~ /^meta$/ || $+ =~ /^\s*$/);
}
but we don't have to write all of that. Some people hate it; others like me think it's absolute genius
Your non-working code
my $key_value;
for $i (keys %cart_item) {
next if (/^meta$/ || /^\s*$/);
$key_value = $i;
# do stuff
}
does use a loop control control variable $i (bad name for a hash key, by the way). That's all fine except that your regex matches still
my $key_value;
for $i (keys %cart_item) {
next if $i =~ /^meta$/ or $i =~ /^\s*$/;
$key_value = $i;
# do stuff
}
or, better still, stick with $_ and write this
for ( keys %cart_item ) {
next if /^meta$/ or /^\s*$/;
my $key_value = $_;
# do stuff
}

Remove values in an array

This is our array :
my #array = ('?C=N;O=D',
'?C=M;O=A',
'?C=S;O=A',
'?C=D;O=A',
'/lab/blog/wp-content/',
'1-list.txt',
'2014/',
'2015/',
'rbxslider/',
'slideshow-gallery/',
'uigen_2015/'
);
how to remove all values before </lab/blog/wp-content/> value
shift #list until (shift (#list)=~m/wp-contnet/);
i have tried with this way but no result
Another way:
#array = grep /wp-content/../(?!)/, #array;
Close.
shift #array until $array[0] =~ /wp-content/;
Or if there's a chance that there might not be anything to remove,
while ($array[0] !~ /wp-content/) { shift #array; }
You might want to make sure you don't loop forever.
while (#array && $array[0] !~ /wp-content/) { shift #array; }
my $count = 0;
my #keep;
foreach (#array){
$count++ if m|/lab/blog/wp-content/|;
push #keep, $_ if $count > 0;
}
say foreach #keep;
/lab/blog/wp-content/
1-list.txt
2014/
2015/
rbxslider/
slideshow-gallery/
uigen_2015/
You're close, when you use the proper array name and correct the typo in "wp-content", but that doesn't include the 'wp-content' element which appears as though you'd like to do. The following checks the first element before shifting, instead of shifting it off and then checking it:
if (grep /wp-content/, #array){
shift #array until $array[0] =~ /wp-content/;
}
print "$_\n" for #array;
Output:
/lab/blog/wp-content/
1-list.txt
2014/
2015/
rbxslider/
slideshow-gallery/
uigen_2015/

Beginner problems with perl

I have been tasked with correcting bugs in a chunk of Perl code and had a few questions about it (as I am new to Perl).
My first issue is what does the exclamation mark do?
if (!$superceded{returned->{$field}}) {
$found = 0;
foreach (blahblahblah)
My second problem is what does it mean if you have a variable $supercede and a hash %superceded and you write
(keys %$superceded)
Finally I have read up on these next two but I am still unsure on how they work. Setting a variable equal to shift and how to use "last;".
Thanks for any help and advice.
! is logical negation, which means if $cond is true, !$cond will be false. You could learn more about Truth and Falsehood from perlsyn.
If $hashref is a hash reference, then %$hashref is the hash that that reference referred to. For example,
my %hash = ( key1 => "val1", key2 => "val2" );
my $hashref = \%hash; # create a hash reference
while (my ($key, $val) = each %$hashref) {
# do something
}
Oh, you also could write something like this
my $hash = \%hash;
A little confusion to human readers, but Perl will accept it without any problem. To Perl, $hash and %hash are two completely different variables, and could be totally unrelated.
You could learn more about reference from perlref. And #$arrayref is similar, except in this case $arrayref is an array reference, it could be created by $arrayref = \#array;.
Setting a variable equal to shift
Do you mean something like this:
my $val = shift;
In this case, this means my $val = shift #_;, which will remove the first element of #_ and assign it to $val. See perldoc -f shift for further details.
how to use "last;"
If you want to finish a loop early, you could use last. For example,
foreach my $i (1..100) {
print "$i\n";
}
will print 1 to 100, and this
foreach my $i (1..100) {
print "$i\n";
last if $i == 5;
}
will only print 1 to 5. See perlsyn for further details.

How can I loop over an array from the first occurrence of an element with a specific value using perl?

I have an array like ("valueA", "valueB", "valueC", "valueD") etc. I want to loop over the values of the array starting from (for example) the first instance of "valueC". Everything in the array before the first instance of the value "valueC" should be ignored; so in this case only "valueC" and "valueD" would be handled by the loop.
I can just put a conditional inside my loop, but is there a neater way to express the idea using perl?
my $seen;
for ( grep $seen ||= ($_ eq "valueC"), #array ) {
...
}
I think you also need to check if the "valueC" exist inside the array.
Hope this helps.
use strict;
use warnings;
use List::Util qw(first);
my #array = qw(valueA valueB valueC valueD);
my $starting_element = 'valueC';
# make sure that the starting element exist inside the array
# first search for the first occurrence of the $stating_element
# dies if not found
my $starting_index = first { $array[$_] eq $starting_element } 0 .. $#array
or die "element \"$starting_element\" does not exist inside the array";
# your loop
for my $index ($starting_index .. $#array) {
print $array[$index]."\n";
}
my $seen;
for ( #array ) {
$seen++ if /valueC/;
next unless $seen;
...
}
But that $seen is a little ungainly. The flip-flop operator looks tidier IMO:
for ( #array ) {
next unless /^valueC$/ .. /\0/;
# or /^valueC$/ .. '' !~ /^$;
# or $_ eq 'valueC' .. /\0/;
...
}
Or simply (building on ikegami's suggestion):
for ( grep { /^valueC$/ .. /(*FAIL)/ } #array ) { ... }
use List::MoreUtils qw( first_index );
foreach my $item ( #array[ ( first_index { $_ eq 'ValueC' } #array ) .. $#array ] ){
# process $item
}
my $start = 0;
++$start while $start < #array && $array[$start] ne 'valueC';
followed by either
for (#array[$start..$#array]) {
say;
}
or
for my $i ($start..$#array) {
say $array[$i];
}
TIMTOWTDI, but I think that:
foreach my $item (#list) {
next if !$seen && ($item ne 'valueC');
$seen++;
...
}
is both readable, correct and and terse enough. All the /valueC/ solution will process anything after "DooDadvalueCFuBAr", not what the OP asked. And, no you need no flipflop/range operator, and checking for the existence beforehand is really strange, besides requiring a possibly noncore package to perform a rather trivial task.The grep solution is really making my head spin, besides creating and tossing a temp array as a side effect.
If you want to get fancy and avoid ''ifs':
foreach my $item (#list) {
$seen || ($item eq 'valueC') || next;
$seen++;
...
}
Just don't write home about it. :-)

Resources