Cara menjumlahkan dua daftar berdasarkan elemen

Saya ingin mengurai file baris demi baris, yang masing-masing berisi dua bilangan bulat, lalu menjumlahkan nilai-nilai ini dalam dua variabel berbeda. Pendekatan naif saya adalah seperti ini:

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

Tapi itu menghasilkan peringatan berikut:

Penggunaan variabel pribadi yang tidak berguna dalam konteks kosong

mengisyaratkan bahwa menggunakan operator += memicu evaluasi sisi kiri dalam skalar alih-alih konteks daftar (mohon koreksi saya jika saya salah dalam hal ini).

Apakah mungkin untuk mencapai hal ini secara elegan (mungkin dalam satu baris) tanpa menggunakan array atau variabel perantara?


Pertanyaan terkait: Bagaimana cara menjumlahkan array berdasarkan elemen di Perl?


person parras    schedule 23.05.2012    source sumber


Jawaban (4)


Tidak, itu karena ekspresi ($i, $j) += (something, 1) diurai sebagai penambahan 1 ke $j saja, meninggalkan $i menggantung dalam konteks kosong. Perl 5 tidak memiliki hyper-operator atau zip otomatis untuk operator penugasan seperti +=. Ini bekerja:

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

Anda dapat menghindari pengulangan dengan menggunakan struktur data gabungan, bukan variabel bernama untuk kolom.

person daxim    schedule 23.05.2012
comment
apa yang kamu maksud dengan kalimat terakhirmu? Array dua elemen, misalnya, masih memerlukan pernyataan aritmatika terpisah, kecuali Anda berpikir untuk membebani +=? - person Borodin; 23.05.2012
comment
Sesuatu seperti array dalam jawaban kratenko. Itu - ditambah dengan pemisahan tanpa batas - berfungsi untuk sejumlah kolom berapa pun. - person daxim; 23.05.2012

Pertama-tama, cara Anda menambahkan array secara berpasangan tidak berfungsi (pertanyaan terkait yang Anda posting sendiri memberikan beberapa petunjuk di sana).

Dan untuk bagian parsing: Bagaimana kalau membagi baris saja? Jika baris Anda diformat sesuai (spasi putih seharusnya tidak menjadi masalah).

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

Jika Anda benar-benar ingin melakukannya dalam satu baris, Anda dapat melakukan sesuatu seperti ini (walaupun menurut saya Anda tidak akan menyebutnya elegan):

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

Untuk masukan @lines = ("11\t1\n", " 22 \t 2 \n", "33\t3"); itu memberi saya @a = (6, 66)

Saya menyarankan Anda untuk menggunakan bagian terpisah dari jawaban saya, tetapi bukan bagian penjumlahan. Tidak ada salahnya menggunakan lebih dari satu baris! Jika itu membuat niat Anda lebih jelas, lebih banyak garis lebih baik daripada satu. Tapi sekali lagi saya jarang menggunakan Perl saat ini melainkan python, jadi gaya pengkodean Perl saya mungkin memiliki pengaruh "buruk" di sana...

person kratenko    schedule 23.05.2012
comment
Ekspresi map itu benar-benar Perl yang cromulent, tidak ada maksud yang jelas. +1 - person daxim; 23.05.2012
comment
@daxim Hej, jadi saya belajar kata baru hari ini, cromulent. Terima kasih telah memperparah kosa kata saya. - person kratenko; 23.05.2012
comment
Batasan 2 dalam panggilan split mengasumsikan hanya ada dua bidang per catatan. Jika tidak, Anda akan mendapatkan (field1, rest-of-record). Sekalipun hanya ada dua bidang, hal ini tidak ada gunanya, dan juga meninggalkan baris baru di akhir bidang kedua. - person Borodin; 23.05.2012
comment
@borodin baris baru di akhir seharusnya tidak menjadi masalah (berfungsi ketika saya mencobanya). Tapi poin bagusnya, itu harus bisa membuatnya berfungsi untuk jumlah kolom (konstan). Saya akan mencobanya, ketika saya kembali ke komputer saya sendiri. - person kratenko; 23.05.2012
comment
+1 untuk bagian yang terpisah. Dan saya sepenuhnya setuju dengan Jika itu membuat niat Anda lebih jelas, lebih banyak garis lebih baik daripada satu. - person parras; 24.05.2012

Sangat mungkin untuk menukar pasangan untuk setiap penambahan, artinya Anda selalu menambahkan elemen yang sama di setiap pasangan. (Ini berlaku umum untuk memutar array multi-elemen jika diperlukan.)

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

keluaran

129 71
person Borodin    schedule 23.05.2012

Inilah pilihan lainnya:

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

Keluaran:

16 - 20
person Kenosis    schedule 23.05.2012