วิธีรวมสองรายการตามองค์ประกอบ

ฉันต้องการแยกวิเคราะห์ไฟล์ทีละบรรทัด ซึ่งแต่ละไฟล์มีจำนวนเต็มสองตัว จากนั้นรวมค่าเหล่านี้เป็นตัวแปรที่แตกต่างกันสองตัว แนวทางที่ไร้เดียงสาของฉันเป็นเช่นนี้:

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
เพียงแค่บางอย่างเช่นอาร์เรย์ในคำตอบของ Kratenko ควบคู่ไปกับการแยกโดยไม่มีขีดจำกัด ใช้ได้กับคอลัมน์จำนวนเท่าใดก็ได้ - 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 วันนี้ฉันได้เรียนรู้คำศัพท์ใหม่ cromulent ขอบคุณที่รวบรวมคำศัพท์ของฉัน - 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