How to create multiple multi-dimensional arrays from STDIN in perl? - arrays

So I'm getting input from STDIN like:
1 2 3
4 5 6
7 6 3
4 3 2
2 3 5
2 5 1
Blank lines separate the matrices, so the above input should create two multi-dimensional arrays...I know how to create one (code below), but how do I create multiple ones depending on how many blank lines the user inputs?
I won't know how many arrays the user wants to create so how can I dynamically create arrays depending on the blank lines in the user input?
my #arrayrefs;
while(<>)
{
chomp;
my #data = split(/\s+/,$_);
push #arrayrefs, \#data;
}
for $ref (#arrayrefs){
print "[#$ref] \n";
}

With your data, I'd say using paragraph mode for the input stream would be a good idea. That is basically setting the input record separator $/ to "\n\n", but in this case we will use "", which is a bit more magical in that it is flexible with extra blank lines.
use strict;
use warnings;
use Data::Dumper;
sub parse_data {
my #matrix = map { [ split / / ] } split /\n/, shift;
return \#matrix;
}
my #array;
$/ = "";
while (<>) {
push #array, parse_data($_);
}
print Dumper \#array;
The map/split statement is not as complex as it looks. Reading from right to left:
shift an argument from the argument list #_
split that argument on newline
take each those (i.e. map them) split arguments and split them again on space, and put the result inside an anonymous array, using brackets [ ].
All done.

It won't win any Code Golf competition, but it does seem to work:
$ cat data
1 2 3
4 5 6
7 6 3
4 3 2
2 3 5
2 5 1
$ cat xx.pl
#!/usr/bin/env perl
use strict;
use warnings;
my #matrices;
my #matrix;
sub print_matrices()
{
print "Matrix dump\n";
foreach my $mref (#matrices)
{
foreach my $rref (#{$mref})
{
foreach my $num (#{$rref})
{
print " $num";
}
print "\n";
}
print "\n";
}
}
while(<>)
{
chomp;
if ($_ eq "")
{
my(#result) = #matrix;
push #matrices, \#result;
#matrix = ();
}
else
{
my #row = split(/\s+/,$_);
push #matrix, \#row;
}
}
# In case the last line of the file is not a blank line
if (scalar(#matrix) != 0)
{
my(#result) = #matrix;
push #matrices, \#result;
#matrix = ();
}
print_matrices();
$ perl xx.pl data
Matrix dump
1 2 3
4 5 6
7 6 3
4 3 2
2 3 5
2 5 1
$

#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
my #arrays = [];
while (<>) {
if (my #array = /(\d+)/g) {
push $arrays[$#arrays], \#array;
} else {
push #arrays, [];
}
}
$Data::Dumper::Indent = 0;
printf("%s\n", Dumper $arrays[0]);
printf("%s\n", Dumper $arrays[1]);
Output:
$VAR1 = [['1','2','3'],['4','5','6'],['7','6','3']];
$VAR1 = [['4','3','2'],['2','3','5'],['2','5','1']];

Related

Perl: Separate array value by certain variable string

Need advice on how to separate array data into different column base on certain string. Like example below base on "EXIT" to split data & print into different column. Thank.
Example:
Input
John
Eva
Felix
Exit
a
b
c
Exit
1
2
3
output
John a 1
Eve b 2
Felix c 3
Iterate over the elements, store them into an array of arrays, resetting the index of the outer array on each Exit:
#!/usr/bin/perl
use warnings;
use strict;
use feature qw{ say };
my #arr = qw(John Eva Felix Exit a b c Exit 1 2 3);
my #out;
my $index = 0;
for (#arr) {
if ('Exit' eq $_) {
$index = 0;
} else {
push #{ $out[$index++] }, $_;
}
}
say join ' ', #$_ for #out;
If the input lines aren't of the same length, you can assign to the particular element in the array:
#!/usr/bin/perl
use warnings;
use strict;
use feature qw{ say };
my #arr = qw(John Eva Felix Exit a b c d e f Exit 1 2 3 4);
my #out;
my $outer = 0;
my $inner = 0;
for (#arr) {
if ('Exit' eq $_) {
$outer = 0;
++$inner;
} else {
$out[$outer++][$inner] = $_;
}
}
say join "\t", map $_ // q(), #$_ for #out;

Referencing an element in a 2D array in Perl

I have the following code which reads in a 6x6 array from STDIN and saves it as an array of anonymous arrays. I am trying to print out each element with $arr[i][j], but the code below isn't working. It just prints out the first element over and over. How am I not accessing the element correctly?
#!/user/bin/perl
my $arr_i = 0;
my #arr = ();
while ($arr_i < 6){
my $arr_temp = <STDIN>;
my #arr_t = split / /, $arr_temp;
chomp #arr_t;
push #arr,\#arr_t;
$arr_i++;
}
foreach my $i (0..5){
foreach my $j (0..5){
print $arr[i][j] . "\n";
}
}
i and j are not the same as the variables you declared in the foreach lines. Change:
print $arr[i][j] . "\n";
to:
print $arr[$i][$j] . "\n";
warnings alerted me to this issue. You should add these lines to all your Perl code:
use warnings;
use strict;
To demonstrate the Perlish mantra that there's "more than one way to do it":
use 5.10.0; # so can use "say"
use strict;
use warnings qw(all);
sub get_data {
my ($cols, $rows) = #_;
my ($line, #rows);
my $i;
for ($i = 1; $i <= $rows and $line = <DATA>; $i++) {
chomp $line;
my $cells = [ split ' ', $line ];
die "Row $i had ", scalar(#$cells), " instead of $cols" if #$cells != $cols;
push #rows, $cells;
}
die "Not enough rows, got ", $i - 1, "\n" if $i != $rows + 1;
\#rows;
}
sub print_data {
my ($cols, $rows, $data) = #_;
for (my $i = 0; $i < $rows; $i++) {
for (my $j = 0; $j < $cols; $j++) {
say $data->[$i][$j];
}
}
}
my $data = get_data(6, 6);
print_data(6, 6, $data);
__DATA__
1 2 3 4 5 6
a b c d e f
6 5 4 3 2 1
f e d c b a
A B C D E F
7 8 9 10 11 12
Explanation:
if we use say, that avoids unsightly print ..., "\n"
get_data is a function that can be called and/or reused, instead of just being part of the main script
get_data knows what data-shape it expects and throws an error if it doesn't get it
[ ... ] creates an anonymous array and returns a reference to it
get_data returns an array-reference so data isn't copied
print_data is a function too
both functions use a conventional for loop instead of making lists of numbers, which in Perl 5 needs to allocate memory
There is also a two-line version of the program (with surrounding bits, and test data):
use 5.10.0; # so can use "say"
my #lines = map { [ split ' ', <DATA> ] } (1..6);
map { say join ' ', map qq{"$_"}, #$_ } #lines;
__DATA__
1 2 3 4 5 6
a b c d e f
6 5 4 3 2 1
f e d c b a
A B C D E F
7 8 9 10 11 12
Explanation:
using map is the premier way to iterate over lists of things where you don't need to know how many you've seen (otherwise, a for loop is needed)
the adding of " around the cell contents is only to prove they've been processed. Otherwise the second line could just be: map { say join ' ', #$_ } #lines;

perl: split array into matches and non-matches

I know you can use grep to filter an array based on a boolean condition. However, I want to get 2 arrays back: 1 for elements that match the condition and 1 for elements that fail. For example, instead of this, which requires iterating over the list twice:
my #arr = (1,2,3,4,5);
my #evens = grep { $_%2==0 } #arr;
my #odds = grep { $_%2!=0 } #arr;
I'd like something like this:
my #arr = (1,2,3,4,5);
my ($evens, $odds) = magic { $_%2==0 } #arr;
Where magic returns 2 arrayrefs or something. Does such an operator exist, or do I need to write it myself?
It's probably most succinct to simply push each value to the correct array in a for loop
use strict;
use warnings 'all';
my #arr = 1 .. 5;
my ( $odds, $evens );
push #{ $_ % 2 ? $odds : $evens }, $_ for #arr;
print "#$_\n" for $odds, $evens;
output
1 3 5
2 4
List::UtilsBy::extract_by is like grep but it modifies the input list:
use List::UtilsBy 'extract_by';
my #arr = (1,2,3,4,5);
my #evens = #arr;
my #odds = extract_by { $_ % 2 } #evens;
print "#evens\n#odds\n";
Output:
2 4
1 3 5
There is also List::UtilsBy::partition_by:
my %parts = partition_by { $_ % 2 } #arr;
#evens = #{$parts{0}}; # (2,4)
#odds = #{$parts{1}}; # (1,3,5)

How to print a particular column from tabular data

I'm Trying to print columns from data by using index key value in the outer part of a foreach loop.
my #col;
foreach(<DATA>){
#x = split(' ',$_);
#xz = ($x[0],$x[1],$x[2]) ;
#print "$x[0]\n"; This is working but i'm not expect this.
push(#col,#xz);
}
print "$col[0]\n";
__DATA__
7 2 3
3 2 8
6 7 2
I expect the output is
7 3 6
How can i do it?
Always use use strict; and use warnings;!!
You have a couple of issues:
push( #col, #xz );
In this case, you're losing your information in #xz array. After this loop, you end up with a single array that looks like this:
#col = ( 7, 2, 3, 3, 2, 8, 6, 7, 2);
So, when you print:
print "$col[0]\n";
You get that zeroth element: 7.
We can preserve the structure of the data by using a reference:
#! /usr/bin/env perl
#
use strict; # Lets you know when you misspell variable names
use warnings; # Warns of issues (using undefined variables
use feature qw(say);
use Data::Dumper;
my #columns;
for my $data ( <DATA> ) {
my #data_list = split /\s+/, $data;
push #columns, \#data_list;
}
say Dumper \#columns;
__DATA__
7 2 3
3 2 8
6 7 2
Here you see I've included Data::Dumper to print out the structure of #columns:
$VAR1 = [
[
'7',
'2',
'3'
],
[
'3',
'2',
'8'
],
[
'6',
'7',
'2'
]
];
As you can see, each entry in the #columns array is now another array. However, printing out $columns[0] array reference isn't going to print what you want. Instead, it's going to print the zeroth array reference: 7, 2, 3, and not the zeroth element of each array reference: 7, 3, 6.
To do that, we need a subroutine that will go through #columns and print out the the zeroth entry of each of the arrays. Here I'm creating a subroutine called fetch_index that will fetch the passed index of the passed array:
#! /usr/bin/env perl
#
use strict; # Lets you know when you misspell variable names
use warnings; # Warns of issues (using undefined variables
use feature qw(say);
use Data::Dumper;
my #columns;
for my $data ( <DATA> ) {
my #data_list = split /\s*/, $data;
push #columns, \#data_list;
}
say join ", ", fetch_index( 0, #columns );
sub fetch_index {
my $entry = shift; #Entry you want from all arrays
my #array = #_;
my #values;
for my $array_ref ( #array ) {
push #values, ${array_ref}->[$entry];
}
return #values;
}
__DATA__
7 2 3
3 2 8
6 7 2
The subroutine merely goes through each array reference I've stored in my array, and fetched the $entry value from that array reference. I push those into my #values array and return that.
my #col;
while (<DATA>) {
push #col, (split ' ')[0];
# push #col, /(\S+)/; # split alternative
}
print "#col\n";
__DATA__
7 2 3
3 2 8
6 7 2
output
7 3 6
Once you've absorbed the information about anonymous arrays and references in the other excellent posts here, you can start to have fun. e.g. you can often get a one liner approach to work:
perl -nE 'say [split]->[1] ' col_data.txt
would loop (-n creates an implicit while(){} loop) through the data in col_data.txt, split the topic variable ($_) creating a series of anonymous arrays from each row and then print the second element, or "column" for example.
You can use the autosplit command line option (-a) to split each row into an array called #F (mnemonic: "F" for "Field"). In later versions of perl, the -a implies the implicit while loop (-n):
perl -anE 'say $F[1] ' col_data.txt
would be the equivalent of the previous command - printing the second column:
output:
2
2
7
There is a famous and short perl workalike for cut that is a more featureful variaton on this theme, and there is this Perl Monks thread.
perl -a -F' ' -ne 'print "$F[0]\n";' data.txt
here you $F[0] is field you can change it accordingly you will get the expected output
You were pretty close I think. This is what I did (edited to reflect comments from #Borodin):
use strict;
use warnings;
sub getColumn {
my ($data, $col) = #_;
my #output = map $_->[$col], #{$data};
return #output;
}
my #data;
while (<DATA>){
push(#data, [split(' ',$_)]);
}
print join(' ', getColumn(\#data, 0), "\n");
print join(' ', getColumn(\#data, 1), "\n");
print join(' ', getColumn(\#data, 2), "\n");
__DATA__
7 2 3
3 2 8
6 7 2
That subroutine getColumn should let you retrieve any arbitrary column. When I ran it with your data I got this for output:
7 3 6
2 2 7
3 8 2

How can I create a two-dimensional array in Perl?

I am currently trying to pass a 32 by 48 matrix file to a multi-dimensional array in Perl. I am able to access all of the values, but I am having issues accessing a specific value.
Here is a link to the data set:
http://paste-it.net/public/x1d5301/
Here is what I have for code right now.
#!/usr/bin/perl
open FILE, "testset.txt" or die $!;
my #lines = <FILE>;
my $size = scalar #lines;
my #matrix = (1 .. 32);
my $i = 0;
my $j = 0;
my #micro;
foreach ($matrix)
{
foreach ($lines)
{
push #{$micro[$matrix]}, $lines;
}
}
It doesn't seem you understand that $matrix only indicates #matrix when it is immediately followed by an array indexer: [ $slot ]. Otherwise, $matrix is a completely different variable from #matrix (and both different from %matrix as well). See perldata.
#!/usr/bin/perl
use English;
Don't! use English--that way!
This brings in $MATCH, $PREMATCH, and $POSTMATCH and incurs the dreaded $&, $`, $' penalty. You should wait until you're using an English variable and then just import that.
open FILE, "testset.txt" or die $!;
Two things: 1) use lexical file handles, and 2) use the three-argument open.
my #lines = <FILE>;
As long as I'm picking: Don't slurp big files. (Not the case here, but it's a good warning.)
my $size = scalar #lines;
my #matrix = (1 .. 32);
my $i = 0;
my $j = 0;
my #micro;
I see we're at the "PROFIT!!" stage here...
foreach ($matrix) {
You don't have a variable $matrix; you have a variable #matrix.
foreach ($lines) {
The same thing is true with $lines.
push #{ $micro[$matrix]}, $lines;
}
}
Rewrite:
use strict;
use warnings;
use English qw<$OS_ERROR>; # $!
open( my $input, '<', 'testset.txt' ) or die $OS_ERROR;
# I'm going to assume space-delimited, since you don't show
my #matrix;
# while ( defined( $_ = <$input> ))...
while ( <$input> ) {
chomp; # strip off the record separator
# Load each slot of #matrix with a reference to an array filled with
# the line split by spaces.
push #matrix, [ split ]; # split = split( ' ', $_ )
}
If you are going to be doing quite a bit of math, you might consider PDL (the Perl Data Language). You can easily set up your matrix and before operations on it:
use 5.010;
use PDL;
use PDL::Matrix;
my #rows;
while( <DATA> ) {
chomp;
my #row = split /\s+/;
push #rows, \#row;
}
my $a = PDL::Matrix->pdl( \#rows );
say "Start ", $a;
$a->index2d( 1, 2 ) .= 999;
say "(1,2) to 999 ", $a;
$a++;
say "Increment all ", $a;
__DATA__
1 2 3
4 5 6
7 8 9
2 3 4
The output shows the matrix evolution:
Start
[
[1 2 3]
[4 5 6]
[7 8 9]
[2 3 4]
]
(1,2) to 999
[
[ 1 2 3]
[ 4 5 999]
[ 7 8 9]
[ 2 3 4]
]
Increment all
[
[ 2 3 4]
[ 5 6 1000]
[ 8 9 10]
[ 3 4 5]
]
There's quite a bit of power to run arbitrary and complex operations on every member of the matrix just like I added 1 to every member. You completely skip the looping acrobatics.
Not only that, PDL does a lot of special stuff to make math really fast and to have a low memory footprint. Some of the stuff you want to do may already be implemented.
You probably need to chomp the values:
chomp( my #lines = <FILE> );
To clarify a tangential point to Axeman's answer:
See perldoc -f split:
A split on /\s+/ is like a split(' ') except that any leading whitespace produces a null first field. A split with no arguments really does a split(' ', $_) internally.
#!/usr/bin/perl
use YAML;
$_ = "\t1 2\n3\f4\r5\n";
print Dump { 'split' => [ split ] },
{ "split ' '" => [ split ' ' ] },
{ 'split /\s+/' => [ split /\s+/ ] }
;
Output:
---
split:
- 1
- 2
- 3
- 4
- 5
---
split ' ':
- 1
- 2
- 3
- 4
- 5
---
split /\s+/:
- ''
- 1
- 2
- 3
- 4
- 5
I see the question is pretty old, but as the author has just edited the question, perhaps this is still of interest. Also the link to the data is dead, but since other answers use space as the separator, I will too.
This answer demonstrates Tie::Array::CSV which allows random access to a CSV (or other file parsable with Text::CSV).
#!/usr/bin/env perl
use strict;
use warnings;
use Tie::Array::CSV;
## put DATA into temporary file
## if not using DATA, put file name in $file
use File::Temp ();
my $file = File::Temp->new();
print $file <DATA>;
##
tie my #data, 'Tie::Array::CSV', $file, {
text_csv => {
sep_char => " ",
},
};
print $data[1][2];
__DATA__
1 2 3 4 5
6 7 8 9 1
2 3 4 5 6

Resources