Reading numbers from a file to variables (Perl)


I've been trying to write a program to read columns of text-formatted numbers into Perl variables.

Basically, I have a file with descriptions and numbers:

ref   5.25676      0.526231      6.325135
ref   1.76234     12.62341       9.1612345


I'd like to put the numbers into variables with different names, e.g.



Here's what I've got so far:

print "Loading file ...";
open (FILE, "somefile.txt");
close FILE;
print "Done!\n";
my $count=0;
foreach $line (@text){
    @coord[$count]=split(/ +/, $line);

I'm trying to compare the positions written in the file to each other, so will need another loop after this.

Sorry, you weren't terribly clear on what you're trying to do and what "ref" refers to. If I misunderstood your problem please commend and clarify.

First of all, I would strongly recommend against using variable names to structure data (e.g. using $ref_1_x to store x coordinate for the first row with label "ref").

If you want to store x, y and z coordinates, you can do so as an array of 3 elements, pretty much like you did - the only difference is that you want to store an array reference (you can't store an array as a value in another array in Perl):

my ($first_column, @data) = split(/ +/, $line); # Remove first "ref" column
@coordinates[$count++] = \@data; # Store the reference to coordinate array

Then, to access the x coordinate for row 2, you do:

$coordinates[1]->[0]; # index 1 for row 2; then sub-index 0 for x coordinate.

If you insist on storing the 3 coordinates in named data structure, because sub-index 0 for x coordinate looks less readable - which is a valid concern in general but not really an issue with 3 columns - use a hash instead of array:

my ($first_column, @data) = split(/ +/, $line); # Remove first "ref" column
@coordinates[$count++] = { x => $data[0], y => $data[1], z => $data[2] };
# curly braces - {} - to store hash reference again

Then, to access the x coordinate for row 2, you do:

$coordinates[1]->{x}; # index 1 for row 2

Now, if you ALSO want to store the rows that have a first column value "ref" in a separate "ref"-labelled data structure, you can do that by wrapping the original @coordinates array into being a value in a hash with a key of "ref".

my ($label, @data) = split(/ +/, $line); # Save first "ref" label
$coordinates{$label} ||= []; # Assign an empty array ref
                          #if we did not create the array for a given label yet.
push @{ $coordinates{$label} }, { x => $data[0], y => $data[1], z => $data[2] };
# Since we don't want to bother counting per individual label,
# Simply push the coordinate hash at the end of appropriate array.
# Since coordinate array is stored as an array reference,
# we must dereference for push() to work using @{ MY_ARRAY_REF } syntax

Then, to access the x coordinate for row 2 for label "ref", you do:

$label = "ref";
$coordinates{$label}->[1]->{x}; # index 1 for row 2 for $label

Also, your original example code has a couple of outdated idioms that you may want to write in a better style (use 3-argument form of open(), check for errors on IO operations like open(); use of lexical filehandles; storing entire file in a big array instead of reading line by line).

Here's a slightly modified version:

use strict;
my %coordinates;
print "Loading file ...";
open (my $file, "<", "somefile.txt") || die "Can't read file somefile.txt: $!";
while (<$file>) {
    my ($label, @data) = split(/ +/); # Splitting $_ where while puts next line
    $coordinates{$label} ||= []; # Assign empty array ref if not yet assigned
    push @{ $coordinates{$label} }
       , { x => $data[0], y => $data[1], z => $data[2] };
print "Done!\n";

It is not clear what you want to compare to what, so can't advise on that without further clarifications.