2

I have multiple arrays (~32). I want to remove all blank elements from them. How can it be done in a short way (may be via one foreach loop or 1-2 command lines)?

I tried the below, but it's not working:

    my @refreshArrayList=("@list1", "@list2", "@list3","@list4", "@list5", "@list6" , "@list7");
    foreach my $i (@refreshArrayList) {
        $i = grep (!/^\s*$/, $i);
    }

Let's say, @list1 = ("abc","def","","ghi"); @list2 = ("qwe","","rty","uy", "iop"), and similarly for other arrays. Now, I want to remove all blank elements from all the arrays.

Desired Output shall be: @list1 = ("abc","def","ghi"); @list2 = ("qwe","rty","uy", "iop") ### All blank elements are removed from all the arrays.

How can it be done?

PPP
  • 329
  • 1
  • 9
  • `@$_ = grep /\S/, @$_ for @AoA;` – zdim Jul 08 '22 at 07:51
  • @zdim, Would you please explain? – PPP Jul 08 '22 at 07:58
  • Then I'd better post it properly, so bear with me for a minute... – zdim Jul 08 '22 at 07:59
  • 1
    Your attempt doesn't work because `"@ary"` (under quotes!) interpolates that array into a string of array elements separated by spaces -- so `("@list1", "@list2",...)` is simply an array of strings, `("a1 b1...", "a2 b2...",...)` and that regex always finds something other than spaces in each of them (as long as an array has at least one element!). – zdim Jul 08 '22 at 08:28

2 Answers2

4

You can create a list of list references and then iterator over these, like

for my $list (\@list1, \@list2, \@list3) {
     @$list = grep (!/^\s*$/, @$list);
}

Of course, you could create this list of list references also dynamically, i.e.

my @list_of_lists;
push @list_of_lists, \@list1;
push @list_of_lists, \@list2;
...
for my $list (@list_of_lists) {
     @$list = grep (!/^\s*$/, @$list);
}
Steffen Ullrich
  • 114,247
  • 10
  • 131
  • 172
4
@$_ = grep /\S/, @$_  for @AoA;   # @AoA = (\@ary1, \@ary2, ...)

Explanation

  • First, this uses the statement modifier, "inverting" the usual for loop syntax into the form STMT for LIST

    The for(each) modifier is an iterator: it executes the statement once for each item in the LIST (with $_ aliased to each item in turn).

    It is mostly equivalent to a "normal" for loop, with the notable difference being that no scope is set and so there is no need to tear it down either, adding a small measure of efficiency. Ae can have only one statement; but then again, that can be a do block. Having no scope means that we cannot declare lexical variables for the statement (unless a do block is used).

    So the statement is @$_ = grep /\S/, @$_, executed for each element of the list.

  • In a for(each) loop, the variable that is set to each element in turn as the list is iterated over ("topicalizer") is an alias to those elements. So changing it changes elements. From perlsyn

    If VAR is omitted, $_ is set to each value.

    If any element of LIST is an lvalue, you can modify it by modifying VAR inside the loop.

    In our case $_ is always an array reference, and then the underlying array is rewritten by dereferencing it (@$_) and assigning to that the output list of grep, which consists only of elements that have at least one non-space character (/\S/).


I ran a three-way benchmark, of the statement-modifier loop against a "normal" loop with and without a topical variable.

For adding 100e6 numbers I get 8-11% speedup (on both a desktop and a server) and with a more involved calculation ($r = ($r + $_ ) / sqrt($_)) it's 4-5%.

A side observation: In both cases the full for loop without a variable (using default $_ for the topicalizer) is 1-2% faster than the one with a lexical topical variable set.

zdim
  • 64,580
  • 5
  • 52
  • 81