verycuriouscat
verycuriouscat

Reputation: 45

How to split array values into to new separate arrays in Perl?

I'am a beginner in learning perl. What I'm trying to do here is to split the array @value and insert it into a new array. my problem is, I do not know exactly how to make my coding to run in loop and get my desired result.

Is it possible to get the desired result using this method or is there any other alternative/way to get the same result?

My code is as below;

my @separated = ();
my @separated1 = ();
my @separated2 = ();
my @separated3 = ();
my $counter = 0;
my @values = "aaa 111 AAA bbb 222 BBB ccc 333 CCC ddd 444 DDD";

foreach (@values) {
my @separated = split(' ', $_);
push @separated1, $separated[0];
push @separated2, $separated[1];
push @separated3, $separated[2];
}
$counter++

print "separated1 = @separated1\n";
print "separated2 = @separated2\n";
print "separated3 = @separated3\n";

Result that I got;

separated1 = aaa

separated2 = 111

separated3 = AAA

Desired Result;

separated1 = aaa bbb ccc ddd

separated2 = 111 222 333 444

separated3 = AAA BB CCC DD

Upvotes: 2

Views: 3200

Answers (6)

zdim
zdim

Reputation: 66873

A rare ocassion where the C-style for loop is suitable, to iterate over every 3rd element

my $string = 'aaa 111 AAA bbb 222 BBB ccc 333 CCC ddd 444 DDD';

my (@sep1, @sep2, @sep3);

my @values = split ' ', $string;

for (my $i=0; $i <= $#values; $i += 3) {
    push @sep1, $values[$i];
    push @sep2, $values[$i+1];
    push @sep3, $values[$i+2];
}

This assumes that the array indeed has all triplets, or better check each element.

But it's normally far nicer to work with a single structure than with a set of parallel arrays. For example, use an array with elements that are array references

use Data::Dump qw(dd);

my @sep;

for (my $i=0; $i <= $#values; $i += 3) { 
    for my $j (0..2) { 
        push @{$sep[$j]}, $values[$i+$j]; 
    }
}

dd \@sep;

where the double iteration can be avoided with a much cleaner

for my $i (0..$#values) { 
    push @{$sep[$i%3]}, $values[$i] 
}

which replaces the two loops.

This prints

[
  ["aaa", "bbb", "ccc", "ddd"],
  [111, 222, 333, 444],
  ["AAA", "BBB", "CCC", "DDD"],
]

I use Data::Dump to see complex data. An alternative in the core is Data::Dumper.


And then there are many modules with all kinds of utility routines for work with lists.

For example, using part from List::MoreUtils to partition the @values array

my @sep = map { [ @values[@$_] ] } part { $_%3 } 0..$#values;

This produces the same @sep with arrayrefs as above.

The part returns a list of arrayrefs, each containing indices since it partitioned the list of indices of @values. Then in map each arrayref is evaluated into its list of indices (@$_), which is used to take the corresponding slice of @values; that list is used to make an arrayref with []. So map returns a list of arrayrefs, with values partitioned as needed.

For working with references please see tutorial perlreftut and reference perlref

Upvotes: 4

G. Cito
G. Cito

Reputation: 6378

I like the solutions from @ikegami and @zdim. @zdim's use of part() from List::MoreUtils reminded me of natatime:

my @values = split(' ', "aaa 111 AAA bbb 222 BBB ccc 333 CCC ddd 444 DDD"); 
use List::MoreUtils 'natatime';              
my $nata_iter = natatime 3, @values ;
my @aoa ;           
while (my @tmp = $nata_iter->()) { push @aoa, \@tmp; };

Not really a consideration but possibly of interest: by using a temporary array (@tmp) to store the output of the iterator, the original @values remains intact whereas the more straightforward splice() is destructive.

Upvotes: 1

Emma
Emma

Reputation: 27723

This expression might help you to get your desired results:

([a-z]+\s)([0-9]+\s)([A-Z]+)

It has three capturing groups for each of your desired results. You can add or reduce boundaries to it, as you wish, and it might be faster than other methods.

enter image description here

Graph

This graph shows how the expression would work and you can visualize other expressions in this link:

enter image description here

JavaScript Test

const regex = /([a-z]+\s)([0-9]+\s)([A-Z]+)/gm;
const str = `aaa 111 AAA bbb 222 BBB ccc 333 CCC ddd 444 DDD`;
const subst = `\n$1 & $2 & $3\n`;

// The substituted value will be contained in the result variable
const result = str.replace(regex, subst);

console.log('Substitution result: ', result);

Perl Test

You can simply use $1, $2 and $3 and separate your data:

use strict;

my $str = 'aaa 111 AAA bbb 222 BBB ccc 333 CCC ddd 444 DDD';
my $regex = qr/([a-z]+\s)([0-9]+\s)([A-Z]+)/mp;
my $subst = '';

my $result = $str =~ s/$regex/$subst/rg;

print "The result of the substitution is' $result\n";

Performance Test

This JavaScript snippet shows the performance of that expression using a simple 1-million times for loop.

const repeat = 1000000;
const start = Date.now();

for (var i = repeat; i >= 0; i--) {
	const string = 'aaa 111 AAA bbb 222 BBB ccc 333 CCC ddd 444 DDD';
	const regex = /([a-z]+\s)([0-9]+\s)([A-Z]+)/gm;
	var match = string.replace(regex, "$1");
}

const end = Date.now() - start;
console.log("YAAAY! \"" + match + "\" is a match 💚💚💚 ");
console.log(end / 1000 + " is the runtime of " + repeat + " times benchmark test. 😳 ");

Upvotes: 2

Grinnz
Grinnz

Reputation: 9231

Another job for List::UtilsBy:

use strict;
use warnings;
use List::UtilsBy 'bundle_by', 'unzip_by';

my $string = 'aaa 111 AAA bbb 222 BBB ccc 333 CCC ddd 444 DDD';
my @vals = split ' ', $string;
my ($sep1, $sep2, $sep3) = unzip_by { @$_ } bundle_by { [@_] } 3, @vals;

print "sep1: @$sep1\nsep2: @$sep2\nsep3: @$sep3\n";

Upvotes: 3

Shawn
Shawn

Reputation: 52334

Another version using part from the non-core but very useful List::MoreUtils module, that partitions up the elements directly:

#!/usr/bin/perl
use warnings;
use strict;
use feature qw/say state/;
use List::MoreUtils qw/part/;

my $str = "aaa 111 AAA bbb 222 BBB ccc 333 CCC ddd 444 DDD";

my ($sep1, $sep2, $sep3) = part { state $i = 0; $i++ % 3 } split(' ', $str);

say "sep1: @$sep1";
say "sep2: @$sep2";
say "sep3: @$sep3";

prints out

sep1: aaa bbb ccc ddd
sep2: 111 222 333 444
sep3: AAA BBB CCC DDD

The magic here is with state, which ends up creating a variable local to the block it's in that retains its value across multiple evaluations of the block.

Upvotes: 3

ikegami
ikegami

Reputation: 385565

my ( @foos, @bars, @quxs );
my @values = split(' ', $input);
while (@values) {
   push @foos, shift(@values);
   push @bars, shift(@values);
   push @quxs, shift(@values);
}

The above can also be written as follows:

my ( @foos, @bars, @quxs );
for ( my @values = split(' ', $input); @values; ) {
   push @foos, shift(@values);
   push @bars, shift(@values);
   push @quxs, shift(@values);
}

Are you sure you want parallel arrays, though? While they can save memory, it's usually hard to work with them, and more error-prone. In an object-dominated landscape, they are virtually never seen.

You could use an AoA:

my @moos;
my @values = split(' ', $input);
while (@values) {
   push @moos, [ splice(@values, 0, 3) ];
}

You could use an AoH:

my @moos;
my @values = split(' ', $input);
while (@values) {
   my %moo; @moo{qw( foo bar qux )} = splice(@values, 0, 3);
   push @moos, \%moo;
}

Upvotes: 4

Related Questions