Как суммировать два списка по элементам

Я хочу проанализировать файл построчно, каждый из которых содержит два целых числа, а затем суммировать эти значения в двух разных переменных. Мой наивный подход был таким:

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

Но это дает следующее предупреждение:

Бесполезное использование частной переменной в пустом контексте

намекая на то, что обращение к оператору += вызывает оценку левой части в скаляре, а не в контексте списка (пожалуйста, поправьте меня, если я ошибаюсь в этом вопросе).

Можно ли добиться этого элегантно (возможно, одной строкой), не прибегая к массивам или промежуточным переменным?


Связанный с этим вопрос: Как я могу суммировать массивы поэлементно в Perl?


person parras    schedule 23.05.2012    source источник


Ответы (4)


Нет, это потому, что выражение ($i, $j) += (something, 1) анализируется как добавление 1 только к $j, оставляя $i висящим в пустом контексте. В Perl 5 нет гипероператоров или автоматического сжатия для таких операторов присваивания, как +=. Это работает:

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

Вы можете избежать повторения, используя составную структуру данных вместо именованных переменных для столбцов.

person daxim    schedule 23.05.2012
comment
что ты имеешь в виду под последней фразой? Например, для двухэлементного массива все равно потребуются отдельные арифметические операторы, если только вы не думаете о перегрузке +=? - person Borodin; 23.05.2012
comment
Просто что-то вроде массива в ответе Кратенко. Это в сочетании с разделением без ограничений работает для любого количества столбцов. - person daxim; 23.05.2012

Прежде всего, ваш способ добавления массивов попарно не работает (связанный вопрос, который вы разместили сами, дает некоторые подсказки).

И для части синтаксического анализа: как насчет того, чтобы просто разделить строки? Если ваши строки отформатированы соответствующим образом (пробелы не должны быть проблемой).

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

Если вы действительно хотите сделать это в одну строку, вы можете сделать что-то вроде этого (хотя я не думаю, что вы назвали бы это элегантным):

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

Для ввода @lines = ("11\t1\n", " 22 \t 2 \n", "33\t3"); он дал мне @a = (6, 66)

Я бы посоветовал вам использовать разделенную часть моего ответа, но не суммирующую часть. Нет ничего плохого в использовании более одной строки! Если это сделает ваше намерение более ясным, то больше строк лучше, чем одна. Но опять же, в настоящее время я почти не использую Perl, а вместо этого использую Python, поэтому мой стиль кодирования Perl может иметь «плохое» влияние...

person kratenko    schedule 23.05.2012
comment
Это выражение map полностью соответствует Perl, ничего неясного в намерениях. +1 - person daxim; 23.05.2012
comment
@daxim Hej, сегодня я выучил новое слово крумулент. Спасибо за расширение моего словарного запаса. - person kratenko; 23.05.2012
comment
Ограничение 2 в вызове split предполагает наличие только двух полей на запись. В противном случае вы получите (field1, rest-of-record). Даже если есть только два поля, это в значительной степени бессмысленно, а также оставляет новую строку в конце второго поля. - person Borodin; 23.05.2012
comment
@borodin новые строки в конце не должны быть проблемой (сработало, когда я пробовал). Но хороший момент, должно быть возможно заставить его работать для любого (постоянного) количества столбцов. Я попробую это, когда вернусь к своему компьютеру. - person kratenko; 23.05.2012
comment
+1 за разделенную часть. И я полностью согласен с вашим Если это делает ваше намерение более ясным, больше строк лучше, чем одна. - person parras; 24.05.2012

Вполне возможно поменять пару местами для каждого добавления, то есть вы всегда добавляете к одному и тому же элементу в каждой паре. (При необходимости это обобщает вращающиеся многоэлементные массивы.)

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

вывод

129 71
person Borodin    schedule 23.05.2012

Вот еще вариант:

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

Вывод:

16 - 20
person Kenosis    schedule 23.05.2012