กรองไฟล์ตามหมายเลขบรรทัด


17

เมื่อกำหนดไฟล์ L ที่มีจำนวนเต็มไม่เป็นลบหนึ่งตัวต่อบรรทัดและไฟล์ข้อความ F จะเป็นวิธีที่รวดเร็วในการเก็บเฉพาะบรรทัดเหล่านั้นใน F ซึ่งมีหมายเลขบรรทัดใดปรากฏในไฟล์ L

ตัวอย่าง:

$ cat L.txt
1
3

$ cat F.txt
Hello World
Hallo Welt
Hola mundo

$ command-in-question -x L.txt F.txt
Hello World
Hola mundo

ฉันกำลังมองหาคำสั่งที่สามารถจัดการไฟล์ L ที่มี 500 ล้านรายการขึ้นไป ไฟล์ L เรียงลำดับตัวเลข

หมายเหตุ: ฉันผ่านการใช้งานไปครึ่งทางแล้วcommand-in-questionแต่ฉันเพิ่งสงสัยว่าอาจใช้เครื่องมือ Unix ได้ที่นี่เช่นกัน


ปรับปรุง: ขอบคุณสำหรับคำตอบทั้งหมดฉันเรียนรู้มากวันนี้! ฉันต้องการยอมรับคำตอบมากกว่าหนึ่งคำตอบ แต่นั่นเป็นไปไม่ได้


ไฟล์ F มีกี่บรรทัด?
Peter.O

1
อย่างน้อยมีหลายบรรทัดใน F เช่นเดียวกับใน L.
miku

คำตอบ:


8

ด้วยการCละเว้นข้อความผิดพลาดที่มีความหมาย:

#include <stdio.h>
#include <stdlib.h>

int main (int argc, char *argv[]) {

    FILE *L;
    FILE *F;

    unsigned int to_print;
    unsigned int current = 0;
    char *line = NULL;
    size_t len = 0;

    if ((L = fopen(argv[1], "r")) == NULL) {
        return 1;
    } else if ((F = fopen(argv[2], "r")) == NULL) {
        fclose(L);
        return 1;
    } else {

        while (fscanf(L, "%u", &to_print) > 0) {
            while (getline(&line, &len, F) != -1 && ++current != to_print);
            if (current == to_print) {
                printf("%s", line);
            }
        }

        free(line);
        fclose(L);
        fclose(F);
        return 0;
    }
}

2
นี่คือคำตอบที่มีประสิทธิภาพมากที่สุดที่นี่ อย่างน้อยก็เป็นเช่นนั้นจากการทดสอบของฉัน xsel -bo | cc -xc - -o cselectในกรณีที่ใครสนใจผมรวบรวมมันเหมือน: และมันใช้งานได้ - มันต้องการแค่สอง libs เท่านั้น
mikeserv

1
ขอบคุณนี่ยอดเยี่ยมมาก! ฉันหวังว่าคุณไม่ทราบ แต่ผมห่อรหัสของคุณขึ้นไปเล็กน้อยเครื่องมือ
miku

1
@miku ไปข้างหน้าฉันดีใจที่ฉันสามารถช่วยได้ ฉันสังเกตเห็นว่าคุณเพิ่มLINE_MAXรุ่นของคุณดังนั้นคุณอาจทำงานกับบรรทัดที่มีขนาดใหญ่มากในไฟล์ของคุณ ฉันได้อัปเดต A ด้วยเวอร์ชันที่ใช้getline()เพื่อลบขีด จำกัด ขนาดบรรทัด
FloHim เอง

@FloHim เองดีขอบคุณอีกครั้ง:) แน่นอนบางบรรทัดอินพุตอาจเกินLINE_MAXดังนั้นgetlineดูเหมือนว่าถูกต้อง
Miku

10

ฉันจะใช้awkแต่ไม่เก็บเนื้อหาทั้งหมดของL.txtในหน่วยความจำและทำการแฮชค้นหาโดยไม่จำเป็น ;-)

list=L.txt file=F.txt
LIST="$list" awk '
  function nextline() {
    if ((getline n < list) <=0) exit
  }
  BEGIN{
    list = ENVIRON["LIST"]
    nextline()
  }
  NR == n {
    print
    nextline()
  }' < "$file"

ฉันลองใช้ hash-maps และหน่วยความจำจะเกิน บิตเซ็ตจะซื้อคุณมากขึ้น headroom; แต่ด้วยการใช้ความจริงที่ว่าอินพุตถูกเรียงลำดับคุณสามารถกำจัดปัญหา (ช่องว่าง) ทั้งหมดได้
miku

1
@Janis; ไม่ใช่กรณีของการฝึกเขียนโค้ดที่ดีแบบมาตรฐาน: อย่าใช้ตัวอักษรรหัสยาก - ใช้ตัวแปรแทน ... (ยืดหยุ่นมากขึ้นและมีข้อผิดพลาดน้อยลงและง่ายต่อการบำรุงรักษา)
Peter.O

1
@ StéphaneChazelas: มันต้องมีการเริ่มต้นวงก่อนnมิฉะนั้น (ตามที่เป็น) มันพลาด1ในL.txt
Peter.O

1
@ Peter.O โอ๊ะนั่นคือสิ่งที่ฉันได้พยายามที่จะพูดกับ NR> = n แต่นั่นผิด ควรจะดีกว่านี้
Stéphane Chazelas

1
@ Janis แนวคิดก็คือหากโค้ดนั้นถูกฝังไว้ในcommand-in-questionสคริปต์คุณจะไม่สามารถฝังชื่อไฟล์ในโค้ดได้ -v list="$opt_x"ไม่ทำงานอย่างใดอย่างหนึ่งเนื่องจากการแบ็กสแลชประมวลผลโดย awk บนมัน นี่คือเหตุผลที่ฉันใช้สภาพแวดล้อมแทนที่นี่
Stéphane Chazelas

10

grep -n | sort | sed | cut

(   export LC_ALL=C
    grep -n ''   | sort -t:  -nmk1,1 ./L - |
    sed /:/d\;n  | cut  -sd: -f2-
)   <./F

ที่ควรจะทำงานได้อย่างรวดเร็ว(การทดสอบตามกำหนดเวลาบางส่วนจะรวมอยู่ด้านล่าง)พร้อมอินพุตทุกขนาด หมายเหตุบางประการเกี่ยวกับวิธีการ:

  • export LC_ALL=C
    • เนื่องจากจุดของการดำเนินการต่อไปนี้คือการทำให้ไฟล์ทั้งหมดของ./Fกองซ้อนในแนวเดียวกันกับ./Lไฟล์ lineno ของมันตัวละครเดียวที่เราต้องกังวลคือ[0-9]ตัวเลขASCII และ:โคลอน
    • ด้วยเหตุนี้จึงง่ายกว่าที่คุณจะกังวลเกี่ยวกับการค้นหาตัวละครทั้ง 11 ตัวในกลุ่มของ 128 สิ่งของมากกว่าที่เป็นถ้ามีส่วนเกี่ยวข้องกับ UTF-8
  • grep -n ''
    • แทรกนี้สตริงLINENO:เข้ามาในหัวของสายในทุก stdin - <./Fหรือ
  • sort -t: -nmk1,1 ./L -
    • sortละเลยที่จะเรียงลำดับไฟล์อินพุตเลยและแทน(อย่างถูกต้อง)ทึกทักเอาว่าพวกมันถูกจัดเรียง-mไว้แล้วและ-numericallyเรียงตามลำดับโดยไม่สนใจสิ่งใด ๆ นอกเหนือจากตัวอักษรโคลอนที่-k1,1เกิดขึ้น-t:
    • ในขณะนี้อาจต้องใช้พื้นที่ชั่วคราวในการทำ(ขึ้นอยู่กับว่าบางลำดับอาจเกิดขึ้นห่างกัน)มันจะไม่ต้องการมากเมื่อเทียบกับการเรียงลำดับที่เหมาะสมและมันจะเร็วมากเพราะมันเกี่ยวข้องกับการย้อนรอยเป็นศูนย์
    • sortจะส่งกระแสข้อมูลเดียวที่มีผ้าปูที่นอนในใด ๆ./Lจะนำหน้าบรรทัดที่เกี่ยวข้อง./Fทันที ./Lเส้นของต้องมาก่อนเสมอเพราะเส้นสั้นกว่า
  • sed /:/d\;n
    • หากบรรทัดปัจจุบันตรงกับ/:/โคลอนdจะลบออกจากเอาต์พุต อื่นพิมพ์อัตโนมัติในปัจจุบันและnต่อสาย
    • ดังนั้นการส่งออกของsedลูกพรุนsortไปยังคู่สายตามลำดับเท่านั้นซึ่งไม่ตรงกับเครื่องหมายโคลอนและบรรทัดต่อไปนี้ - หรือเฉพาะบรรทัดจาก./Lและถัดไปเท่านั้น
  • cut -sd: -f2-
    • cut -sตัวพิมพ์ใหญ่จากเอาต์พุตของบรรทัดอินพุตที่ไม่มี-d:สตริงตัวกำจัดอย่างน้อยหนึ่งตัว- และ./Lบรรทัดของนั้นจะถูกตัดออกอย่างสมบูรณ์
    • สำหรับสายเหล่านั้นที่ทำครั้งแรกของพวกเขา:ลำไส้ใหญ่คั่น-field เป็นcutไป - และอื่น ๆ ไปทั้งหมดของgrep's แทรก LineNo ของ

ทดสอบอินพุตขนาดเล็ก

seq 5 | sed -ne'2,3!w /tmp/L
        s/.*/a-z &\& 0-9/p' >/tmp/F

... สร้างอินพุตตัวอย่าง 5 บรรทัด จากนั้น ...

(   export LC_ALL=C; </tmp/F \
    grep -n ''   | sort -t:  -nmk1,1 ./L - |
    sed /:/d\;n  | cut  -sd: -f2-
)|  head - /tmp[FL]

... พิมพ์ ...

==> standard input <==
a-z 1& 0-9
a-z 4& 0-9
a-z 5& 0-9

==> /tmp/F <==
a-z 1& 0-9
a-z 2& 0-9
a-z 3& 0-9
a-z 4& 0-9
a-z 5& 0-9

==> /tmp/L <==
1
4
5

การทดสอบหมดเวลาที่ใหญ่กว่า

ฉันสร้างไฟล์ขนาดใหญ่สองสามไฟล์:

seq 5000000 | tee /tmp/F |
sort -R | head -n1500000 |
sort -n >/tmp/L

... ซึ่งวางสาย 5mil ใน/tmp/Fและ 1.5mil /tmp/Lสายการสุ่มเลือกของเข้าที่ จากนั้นฉันก็:

time \
(   export LC_ALL=C
    grep -n ''   | sort -t:  -nmk1,1 ./L - |
    sed /:/d\;n  | cut  -sd: -f2-
)   <./F |wc - l

มันพิมพ์:

1500000
grep -n '' \
    0.82s user 0.05s system 73% cpu 1.185 total
sort -t: -nmk1,1 /tmp/L - \
    0.92s user 0.11s system 86% cpu 1.185 total
sed /:/d\;n \
    1.02s user 0.14s system 98% cpu 1.185 total
cut -sd: -f2- \
    0.79s user 0.17s system 80% cpu 1.184 total
wc -l \
    0.05s user 0.07s system 10% cpu 1.183 total

(ฉันเพิ่มแบ็กสแลชที่นั่น)

ในบรรดาโซลูชั่นที่มีอยู่ในปัจจุบันนี่คือวิธีที่เร็วที่สุดของทั้งหมด แต่หนึ่งวิธีเมื่อเทียบกับชุดข้อมูลที่สร้างขึ้นบนเครื่องของฉัน ของคนอื่น ๆ เพียงคนเดียวที่เข้ามาใกล้ต่อสู้เพื่อที่สองและที่เป็น meuh เป็นที่นี่perl

นี่ไม่ใช่วิธีการดั้งเดิมที่เสนอ - มันลดลงหนึ่งในสามของเวลาดำเนินการขอบคุณคำแนะนำ / แรงบันดาลใจจากผู้อื่น ดูประวัติการโพสต์สำหรับการแก้ปัญหาช้า( แต่ทำไม?)

นอกจากนี้ยังเป็นที่น่าสังเกตว่าคำตอบอื่น ๆ อาจจะดีกว่าถ้ามันไม่ใช่สถาปัตยกรรม multi-cpu ของระบบของฉันและการทำงานพร้อมกันของแต่ละกระบวนการในไปป์ไลน์นั้น พวกเขาทั้งหมดทำงานในเวลาเดียวกัน - แต่ละตัวประมวลผลหลัก - ส่งผ่านข้อมูลและทำส่วนเล็ก ๆ ของทั้งหมด มันเจ๋งมาก

แต่ทางออกที่เร็วที่สุดคือ ...

แต่มันไม่ใช่ทางออกที่เร็วที่สุด วิธีที่เร็วที่สุดที่นำเสนอที่นี่มือลงเป็นโปรแกรม C cselectผมเรียกมันว่า หลังจากคัดลอกไปยังคลิปบอร์ด X ของฉันฉันรวบรวมมันเช่น:

xsel -bo | cc -xc - -o cselect

จากนั้นฉันก็:

time \
    ./cselect /tmp/L /tmp/F |
wc -l

... และผลลัพธ์ก็คือ ...

1500000
./cselect /tmp/L /tmp/F  \
    0.50s user 0.05s system 99% cpu 0.551 total
wc -l \
    0.05s user 0.05s system 19% cpu 0.551 total

1
คุณสามารถทำให้เร็วขึ้นอย่างมาก (เกือบเร็วเท่ากับของฉันในระบบมัลติคอร์) ด้วยsed -ne'/:/!{n;p;}' | cut -d: -f2-แทนที่จะเป็นsed -ne'/:/!N;/\n/s/[^:]*://p'
Stéphane Chazelas

@ StéphaneChazelas - คุณอาจได้ผลลัพธ์ที่ดีกว่าถ้าคุณเปลี่ยนseds - สิ่งที่sedฉันใช้คือมรดกสืบทอดsed- คุณสามารถเห็นaliasคุณค่าในtimeผลลัพธ์ได้ แพคเกจมรดกตกทอดของฉันโดยวิธีการที่จะรวบรวมแบบคงที่กับ libc คิดถึง - การดำเนินงานสำหรับ regex ซึ่งจะขึ้นอยู่กับ TRE เมื่อฉันเปลี่ยนมันเป็น GNU sed- และเรียกใช้โดยไม่cut- จะเพิ่มวินาทีเต็มให้กับเวลาที่เสร็จสมบูรณ์(2.8 วินาที) - ผสมมันได้มากกว่าหนึ่งในสาม และนั่นเป็นเพียง 0.3 วินาทีเร็วกว่าของคุณในระบบของฉัน
mikeserv

1
sort -mnเมื่อเทียบกับsort -nmk1,1อาจจะดีกว่าที่คุณไม่จำเป็นต้องแยกที่นี่ (ไม่ผ่านการทดสอบ)
Stéphane Chazelas

@ StéphaneChazelas - ใช่ฉันคิดเหมือนกันและฉันลองทุกวิธี -nเป็น spec'd เพียงเพื่อทำสตริงตัวเลขครั้งแรกในบรรทัดดังนั้นฉันคิด, OK -mnหรือ-nmและด้วยเหตุผลใดครั้งเดียวที่มันเคยต่ำกว่า 2 วินาทีในเวลาที่เสร็จสิ้นเมื่อฉันถูกเพิ่มเข้ามาในตัวเลือกทั้งหมดที่เป็นอยู่ มันแปลก - และเป็นเหตุผลเมื่อวานนี้ที่ฉันไม่ได้ติดตั้ง-mในตอนแรก - ฉันรู้ว่าฉันกำลังทำอะไรอยู่ แต่ดูเหมือนว่ามันจะเป็นเพียงการเพิ่มประสิทธิภาพอัตโนมัติบางอย่าง ที่น่าสนใจมรดกสืบทอดsortมี-zตัวเลือกความยาวสตริงที่ใช้เฉพาะกับ-[cm]...
mikeserv

-nไม่ได้เป็นสตริงตัวเลขแรกในบรรทัด มันแค่คิดว่าเส้นเป็นตัวเลขดังนั้นabc 123จะเป็น 0 ดังนั้นมันจึงไม่มีประสิทธิภาพน้อยกว่าด้วย-t: -k1,1
Stéphane Chazelas

9

ฉันต้องการใช้awk:

awk 'NR==FNR {a[$1]; next}; FNR in a' L.txt F.txt

อัปเดต: ฉันได้ทำการวัดประสิทธิภาพแล้ว ดูเหมือนว่ารุ่นนี้จะปรับขนาดได้ดียิ่งขึ้นด้วยชุดข้อมูลที่มีขนาดใหญ่มาก (เช่นกรณีที่มีข้อกำหนดที่ระบุไว้) เนื่องจากการเปรียบเทียบนั้นรวดเร็วมากและชดเชยความพยายามที่จำเป็นในการสร้างตารางแฮช


1
@miku; ใช่มันเป็นทางออกที่กะทัดรัด แต่ข้อแม้; ไม่ใช่ทุกคนawkที่จะสามารถจัดการกับชุดข้อมูลขนาดใหญ่เช่นนั้นได้ - ฉันใช้ GNU awkและไม่มีปัญหา การทดสอบที่มีข้อมูล 500 ล้านบรรทัดต้องใช้ 7 นาที
Janis

1
นี้ค่อนข้างช้า (โดยเปรียบเทียบ) real 16m3.468s- -user 15m48.447s sys 0m10.725sใช้ RAM ขนาด 3.3 GB ทดสอบขนาด 1 / 10'th Lกับ50,000,000บรรทัด และFมี500,000,000บรรทัด - เวลาเทียบกับผู้สังเกตการณ์ที่น่ากลัวของStéphane Chazelas: real 2m11.637s- user 2m2.748s- sys 0m6.424s- ฉันไม่ได้ใช้กล่องเร็ว แต่การเปรียบเทียบนั้นน่าสนใจ
Peter.O

@ Peter.O; ขอบคุณสำหรับข้อมูล! ความเร็วที่ช้าลงคือการคาดหวังว่า (ในกรณีทดสอบของฉันเอง) ครึ่งพันล้านบรรทัดถูกเก็บไว้ในอาเรย์แบบเชื่อมโยง (นั่นเป็นเหตุผลที่ฉันแสดงความคิดเห็น "(+1)" ด้านบนสำหรับข้อเสนอของ Stephane) - แม้ว่าฉันรู้สึกประหลาดใจที่โซลูชัน terse นี้ยังคงประมวลผล 1 ล้านบรรทัดต่อวินาที! ฉันคิดว่ามันทำให้รูปแบบรหัสนี้ (เพราะมันง่าย!) เป็นตัวเลือกที่ทำงานได้และโดยเฉพาะในกรณีที่มีขนาดข้อมูลน้อยที่สุด
Janis

แน่นอนมันเป็นทางออกที่ทำงานได้ จากข้อมูลการทดสอบฉันใช้(5mil lines / 1.5mil L) ของคุณเสร็จในเวลาน้อยกว่า 4 วินาที - เพียงหนึ่งวินาทีหลังคำตอบของ Stephane รหัสที่ใช้ในการทดสอบ Gen Set อยู่ในคำตอบของฉัน แต่มันเป็นเรื่องส่วนใหญ่เป็นเพียงseqการส่งออกและจากนั้นมีขนาดเล็กเซตสุ่มเลือกของที่เหมือนกันในL
mikeserv

1
ฉันเพิ่งทำมาตรการเพิ่มเติมเกี่ยวกับประสิทธิภาพด้วยขนาดไฟล์ข้อมูล 500 ล้านบรรทัดและขนาดไฟล์สำคัญ 50 ล้านและการตอบสนอง 500 ล้านบรรทัดโดยมีข้อสังเกตที่น่าสังเกต ด้วยไฟล์คีย์ขนาดเล็กเวลาคือ 4 นาที (Stephane) กับ 8 นาที (Janis) ในขณะที่ไฟล์คีย์ที่ใหญ่กว่าคือ 19 นาที (Stephane) กับ 12 นาที (Janis)
Janis

3

เพื่อความสมบูรณ์: เราสามารถรวมสคริปต์ awk ที่ยอดเยี่ยมในคำตอบโดยStéphane Chazelas และสคริปต์ Perl ในคำตอบโดย kos แต่โดยไม่เก็บรายการทั้งหมดไว้ในหน่วยความจำด้วยความหวังว่า Perl อาจเร็วกว่า awk (ฉันได้เปลี่ยนลำดับของ args เพื่อให้ตรงกับคำถามเดิม)

#!/usr/bin/env perl
use strict;

die "Usage: $0 l f\n" if $#ARGV+1 != 2;
open(L,$ARGV[0]) or die "$ARGV[0]: $!";
open(F,$ARGV[1]) or die "$ARGV[1]: $!";

while(my $number = <L>){
    #chop $number;
    while (<F>) {
        if($. == $number){
            print;
            last;
        }
    }
}

awkนี่คือวิธีที่เร็วกว่า มันเร็วพอ ๆ กับของฉัน - ฉันทดสอบทั้งสามครั้งในตอนนี้และทุกครั้งที่ฉันจัดการชุดทดสอบ 5mil line ของฉันใน 1.8 ... วินาทีและของคุณ 1.9 ... วินาทีต่อครั้ง รหัสของชุดทดสอบอยู่ในคำตอบของฉันถ้าคุณสนใจ แต่ประเด็นก็คือมันดีมาก ยิ่งไปกว่านั้นผลลัพธ์ที่ถูกต้อง - ฉันยังคงไม่สามารถawkทำงานได้ ... ถึงกระนั้นทั้งสองคำตอบของเราถูกทำให้น่าอับอายโดยFloHimเอง
mikeserv

@mikeserv เราต้องมีawks ที่แตกต่างกัน ในตัวอย่างของคุณฉันได้รับ 1.4s กับ gawk (4s สำหรับ Janis '), 0.9s กับ mawk, 1.7s กับ perl solution, 2.3s with kos', 4.5s กับคุณ (GNU sed) และ 1.4s กับคุณ ( GNU sed) และการปรับปรุงที่แนะนำของฉัน (และ 0.5s สำหรับโซลูชัน C)
Stéphane Chazelas

@mikeserv, ah! แน่นอนด้วยวิธีการของคุณสถานที่สร้างความแตกต่าง ลดลงจาก 4.5s เป็น 2.3s ที่นี่เมื่อเปลี่ยนจาก UFT-8 เป็น C
Stéphane Chazelas

3

ฉันเขียนสคริปต์ Perl ง่าย ๆ ที่จะทำ:

Usage: script.pl inputfile_f inputfile_f

#!/usr/bin/env perl

$number_arguments = $#ARGV + 1;
if ($number_arguments != 2) {
    die "Usage: script.pl inputfile_f inputfile_l\n";
}

open($f, '<', $ARGV[0])
    or die "$ARGV[0]: Not found\n";
open($l, '<', $ARGV[1])
    or die "$ARGV[1]: Not found\n";

@line_numbers = <$l>;

while ($line = <$f>) {
    $count_f ++;
    if ($count_f == @line_numbers[$count_l]) {
        print $line;
        $count_l ++;
    }
}
  • โหลด F.txt
  • โหลด L.txt
  • ร้านค้าแต่ละบรรทัด L.txtลงในอาร์เรย์
  • อ่านทีF.txtละบรรทัดติดตามหมายเลขบรรทัดปัจจุบันและดัชนีอาร์เรย์ปัจจุบัน เพิ่มF.txtหมายเลขบรรทัดปัจจุบัน หากF.txtหมายเลขบรรทัดปัจจุบันตรงกับเนื้อหาของอาร์เรย์ที่ดัชนีอาร์เรย์ปัจจุบันจะพิมพ์บรรทัดปัจจุบันและเพิ่มดัชนี

ข้อพิจารณาด้านราคาและความซับซ้อน :

เมื่อพิจารณาจากค่าใช้จ่ายที่จะทำให้การกำหนดค่าใช้จ่ายที่จะทำให้รถและค่าใช้จ่ายในการพิมพ์เส้นที่ได้รับ N 1เป็นจำนวนเส้นในF.txtและ N 2เป็นจำนวนบรรทัดในL.txtที่whileห่วงวิ่งมากที่สุด N 1ครั้ง นำไปสู่การมอบหมาย2N 1 + N 2 (โดยชัดว่าสมมติว่า N 1 > N 2 ) เปรียบเทียบกับ2N 1และ N 2ภาพพิมพ์; กำหนดเท่ากับต้นทุนของการดำเนินการแต่ละครั้งค่าใช้จ่ายทั้งหมดในการดำเนินการwhileลูปคือ 4N 1 + 2N 2ซึ่งนำไปสู่ความซับซ้อนของสคริปต์ของ O (N)

ทดสอบไฟล์อินพุต 10 ล้านบรรทัด :

การใช้ไฟล์ 10 ล้านเส้นF.txtที่มีบรรทัดยาว 50 อักขระแบบสุ่มและไฟล์ 10 ล้านบรรทัดL.txtที่มีตัวเลขตั้งแต่ 1 ถึง 10,000,000 (สถานการณ์กรณีที่เลวร้ายที่สุด):

~/tmp$ for ((i=0; i<3; i++)); do time ./script.pl F.txt L.txt > output; done

real    0m15.628s
user    0m13.396s
sys 0m2.180s

real    0m16.001s
user    0m13.376s
sys 0m2.436s

real    0m16.153s
user    0m13.564s
sys 0m2.304s

2

โซลูชัน perl นี้เร็วกว่าโซลูชัน awk หรือ perl อื่น ๆ ประมาณ 20% หรือมากกว่านั้น แต่ไม่เร็วเท่ากับโซลูชันใน C

perl -e '
  open L, shift or die $!;
  open F, shift or die $!;
  exit if ! ($n = <L>);
  while (1) {
    $_ = <F>;
    next if $. != $n;
    print;
    exit if ! ($n = <L>);
  }
' -- L F

0
cat <<! >L.txt
1
3
!

cat <<! >F.txt
Hello World
Hallo Welt
Hola mundo
!

cmd(){
 L=$1 F=$2
 cat -n $F |
 join $L - |
 sed 's/[^ ]* //'
}

cmd L.txt F.txt
Hello World
Hola mundo

เนื่องจาก L.txt ถูกเรียงลำดับคุณสามารถใช้เข้าร่วม เพียงกำหนดหมายเลขแต่ละบรรทัดใน F.txt เข้าร่วมสองไฟล์จากนั้นลบหมายเลขบรรทัด ไม่จำเป็นต้องใช้ไฟล์ขนาดใหญ่

ที่จริงแล้วข้างต้นจะคล้ำสายข้อมูลของคุณโดยแทนที่พื้นที่สีขาวทั้งหมดด้วยช่องว่างเดียว เพื่อให้บรรทัดยังคงเหมือนเดิมคุณต้องเลือกเป็นตัวคั่นบางตัวที่ไม่ปรากฏในข้อมูลของคุณเช่น "|" cmd นั้น

cmd(){
 L=$1 F=$2
 cat -n $F |
 sed 's/^ *//;s/\t/|/' |
 join -t'|' $L - |
 sed 's/[^|]*|//'
}

sed แรกจะลบช่องว่างนำออกจากเอาต์พุต "cat -n" และแทนที่แท็บ รุ่นที่สองจะลบหมายเลขบรรทัดและ "|"


ฉันกลัวว่านี่จะไม่ทำงานกับไฟล์ขนาดใหญ่ มันต้องการ <10 บรรทัด ฉันมีความคิดเดียวกันและลองใช้join L.txt <(nl F.txt )แต่จะไม่ทำงานกับไฟล์ขนาดใหญ่ ยินดีต้อนรับสู่เว็บไซต์อย่างไรก็ตามบ่อยครั้งที่เราได้รับคำตอบที่ชัดเจนและมีรูปแบบที่ดีจากผู้ใช้ใหม่!
terdon

@terdon ใช่ความอัปยศที่join/ commไม่สามารถทำงานกับอินพุตที่เรียงลำดับตัวเลข
Stéphane Chazelas

@terdon: ฉันติดตามลูกค้าของคุณ (ลบตอนนี้) แล้วลองjoin -t' ' <(<L.txt awk '{printf("%010s\n",$0)}') <(<F.txt awk '{printf("%010s %s\n",NR,$0)}') | cut -d' ' -f2-- มันช้า! - และแม้กระทั่งเมื่อฉันป้อนไฟล์ที่เตรียมไว้พร้อมกับคีย์ 0 เบาะที่เหมาะสมjoin -t' ' L.txt F.txt | cut -d' ' -f2- แต่ก็ยังช้า (ไม่รวมเวลาเตรียมการ) - ช้ากว่าawkคำตอบโดย @Janis (ที่ฉันโพสต์ความคิดเห็นอีกครั้งตามเวลาจริงสำหรับทั้งสอง) คำตอบของเขาและ @ StéphaneChazelas
Peter.O

@ Peter.O ใช่ ฉันลองวิธีที่คล้ายกันซึ่งหลีกเลี่ยงหนึ่งใน awks แต่ฉันไม่สามารถหาวิธีที่จะทำให้ทั้งสองทำงานได้และคุ้มค่า
terdon

@terdon และอื่น ๆ : เวลาที่เกิดขึ้นจริงสำหรับjoin+ awk printf substiturion กระบวนการเป็นreal 20m11.663s user 19m35.093s sys 0m10.513s VS Stéphane Chazelas' real 2m11.637s user 2m2.748s sys 0m6.424s ใช้L50 ล้านเส้นF500 ล้านเส้น
Peter.O

0

เพื่อความสมบูรณ์ลองอีกjoinวิธีในการแก้ปัญหา:

sed -r 's/^/00000000000000/;s/[0-9]*([0-9]{15})/\1/' /tmp/L | join <( nl -w15 -nrz /tmp/F ) - | cut -d' ' -f2-

สิ่งนี้ทำงานโดยการจัดรูปแบบคอลัมน์หมายเลขบรรทัดที่เข้าร่วมทำงานเป็นความยาวคงที่ด้วยศูนย์นำหน้าเพื่อให้ตัวเลขมีความยาว 15 หลักเสมอ สิ่งนี้จะหลีกเลี่ยงปัญหาของการเข้าร่วมที่ไม่ชอบลำดับการเรียงเชิงตัวเลขปกติเนื่องจากคอลัมน์ได้ถูกบังคับให้เรียงลำดับพจนานุกรมอย่างมีประสิทธิภาพ nlใช้เพื่อเพิ่มหมายเลขบรรทัดในรูปแบบนี้ไปยัง F.txt น่าเสียดายsedที่ต้องใช้การฟอร์แมตตัวเลขใน L.txt

วิธีการนี้ดูเหมือนว่าจะใช้ได้บนข้อมูลทดสอบที่สร้างขึ้นโดยใช้วิธีการของ @ mikeserv แต่มันยังช้ามาก - โซลูชัน c นั้นเร็วกว่า 60 เท่าบนเครื่องของฉัน เกี่ยวกับ 2/3 ของเวลาที่ใช้ในsedและ 1/3 joinใน อาจมีการแสดงออกที่ดีกว่า ...


ตกลง - แต่ทำไมเราจึงเติมศูนย์ทั้งหมด ฉันพยายามที่จะรู้สึกถึงสิ่งนี้ นอกจากนี้nlมันเจ๋งสุด ๆ แต่คุณไม่สามารถใช้มันกับอินพุตที่ไม่ได้ทดสอบ สิ่งหนึ่งที่ทำให้มันเจ๋งมากก็คือตัวกำจัดหน้าตรรกะ -dตามค่าเริ่มต้นหากมีบรรทัดใด ๆ ในอินพุตที่ประกอบด้วยเฉพาะสตริง:\` (แต่ไม่มีหลุมต่อท้าย) 1, 2, 3 หรือสามครั้งติดต่อกันการนับของคุณจะบ้าไปเล็กน้อย ทดลองกับมัน - มันค่อนข้างเรียบร้อย โดยเฉพาะอย่างยิ่งได้ดูสิ่งที่เกิดขึ้นเมื่อ nl` อ่านบรรทัดที่มี 1 ตัวคั่นสตริงและต่อมาอีก w / 3 หรือ 2
mikeserv

0

เนื่องจากคำตอบที่ได้รับการยอมรับอยู่ใน C ฉันคิดว่ามันก็โอเคที่จะทิ้งโซลูชันหลามไว้ที่นี่:

# Read mask
with open('L.txt', 'r') as f:
    mask = [int(line_num) for line_num in f.read().splitlines()]

# Filter input file
filtered_lines = []
with open('F.txt', 'r') as f:
    for i, line in enumerate(f.read().splitlines()):
        if (i+1) in mask:
            filtered_lines.append(line)

# Write newly filtered file
with open('F_filtered.txt', 'w') as f:
    for line in filtered_lines:
        f.write('%s\n' % line)

หากใช้ไลบรารีภายนอกเช่น numpy โซลูชันจะดูสง่างามยิ่งขึ้น:

import numpy as np

with open('L.txt', 'r') as f:
    mask = np.array([int(line_num)-1 for line_num in f.read().splitlines()])

with open('F.txt', 'r') as f:
    lines = np.array(f.read().splitlines())
filtered_lines = lines[mask]

with open('F_filtered.txt', 'w') as f:
    for line in filtered_lines:
        f.write('%s\n' % line)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.