3

I want to parse a file line by line, each of which containing two integers, then sum these values in two distinct variables. My naive approach was like this:

my $i = 0;
my $j = 0;
foreach my $line (<INFILE>)
{
    ($i, $j) += ($line =~ /(\d+)\t(\d+)/);
}

But it yields the following warning:

Useless use of private variable in void context

hinting that resorting to the += operator triggers evaluation of the left-hand side in scalar instead of list context (please correct me if I'm wrong on this point).

Is it possible to achieve this elegantly (possibly in one line) without resorting to arrays or intermediate variables?


Related question: How can I sum arrays element-wise in Perl?

Community
  • 1
  • 1
parras
  • 177
  • 9

4 Answers4

6

No, it's because the expression ($i, $j) += (something, 1) parses as adding 1 to $j only, leaving $i hanging in void context. Perl 5 has no hyper-operators or automatic zipping for the assignment operators such as +=. This works:

my ($i, $j) = (0, 0);
foreach my $line (<INFILE>) {
    my ($this_i, $this_j) = split /\t/, $line;
    $i += $this_i;
    $j += $this_j;
}

You can avoid the repetion by using a compound data structure instead of named variables for the columns.

daxim
  • 39,270
  • 4
  • 65
  • 132
  • 1
    what do you mean by your last sentence? A two-element array, for instance, would still need separate arithmetic statements, unless you're thinking of overloading `+=`? – Borodin May 23 '12 at 13:27
  • Simply something like the array in kratenko's answer. That - coupled with a split without limits - works for any number of columns. – daxim May 23 '12 at 16:25
3

First of all, your way of adding arrays pairwise does not work (the related question you posted yourself gives some hints there).

And for the parsing part: How about just splitting the lines? If your lines are formatted accordingly (whitespaces should not be a problem).

split(/\t/, $line, 2)

If you really, really want to do it in one line, you could do something like this (though I don't think you would call it elegant):

my @a = (0, 0);
foreach my $line (<INFILE>)
{
    @a = map { shift(@a)+$_ } split(/\t/, $line, 2);
}

For an input of @lines = ("11\t1\n", " 22 \t 2 \n", "33\t3"); it gave me the @a = (6, 66)

I would advise you to use the split part of my answer, but not the adding up part. There is nothing wrong in using more than one line! If it makes your intention clearer, more lines are better than one. But than again I'm hardly using perl nowadays but python instead, so my perl coding style might have a "bad" influence there...

kratenko
  • 7,354
  • 4
  • 36
  • 61
  • 2
    That `map` expression is perfectly cromulent Perl, nothing unclear about the intention. +1 – daxim May 23 '12 at 10:49
  • 1
    @daxim Hej, so I learned a new word today, _cromulent_. Thanks for embiggening my vocabulary. – kratenko May 23 '12 at 10:53
  • The limit of 2 in the `split` call assumes there are only two fields per record. Otherwise you would get `(field1, rest-of-record)`. Even if there are only two fields it is pretty much pointless, and also leaves the newline on the end of the second field. – Borodin May 23 '12 at 13:31
  • @borodin the newlines at the end should not be a problem (worked when I tried it). But good point there, it should be possible to make it work for any (constant) number of columns. I'll try that, when I'm back at my own computer. – kratenko May 23 '12 at 13:35
  • +1 for the split part. And I totally agree with your "_If it makes your intention clearer, more lines are better than one._" – parras May 24 '12 at 07:43
2

It is quite possible to swap the pair over for each addition, meaning you're always adding to the same element in each pair. (This generalises to rotating multi-element arrays if required.)

use strict;
use warnings;

my @pair = (0, 0);

while (<DATA>) {
  @pair = ($pair[1], $pair[0] + $_) for /\d+/g;
}

print "@pair\n";

__DATA__
99 42
12 15
18 14

output

129 71
Borodin
  • 126,100
  • 9
  • 70
  • 144
0

Here's another option:

use Modern::Perl;

my $i = my $j = 0;

map{$i += $_->[0]; $j += $_->[1]} [split] for <DATA>;

say "$i - $j";

__DATA__
1   2
3   4
5   6
7   8

Output:

16 - 20
Kenosis
  • 6,196
  • 1
  • 16
  • 16