เอาต์พุตส่วนหนึ่งของแต่ละบรรทัดไปยังไฟล์แยกกัน


14

ฉันมีไฟล์เช่นนี้:

a   AGTACTTCCAGGAACGGTGCACTCTCC
b   ATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCAT
c   ATATTAAATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCATCCACTCCACAC
d   ATCAGTTTAATATCTGATACGTCCTCTATCCGAGGACAATATATTAAATGGA
e   TTTGGCTAAGATCAAGTGTAGTATCTGTTCTTATAAGTTTAATATCTGATATGTCCTCTATCTGA

ฉันต้องการที่จะทำให้ไฟล์ที่มีลำดับa.seq AGTACTTCCAGGAACGGTGCACTCTCCในทำนองเดียวกันมีb.seq ATGGATTTTTGGAGCAGGGAGATGGAATAGGAGCATGCTCCATในระยะสั้น Column1 ควรใช้เป็นชื่อไฟล์เอาต์พุตที่มีนามสกุล.seqและจากนั้นควรมีลำดับ column2 ที่สอดคล้องกัน ฉันสามารถทำได้โดยการเขียนสคริปต์ Perl แต่สิ่งใดในบรรทัดคำสั่งจะเป็นประโยชน์ หวังว่าจะได้ยินในไม่ช้า

คำตอบ:


16

การตอบสนองอย่างรวดเร็วของฉันน่าจะเกิดขึ้นawkแต่ถ้าคุณกำลังประมวลผลหลายบรรทัด - และฉันกำลังพูดถึงหลายล้านคน - คุณอาจเห็นประโยชน์ที่แท้จริงจากการเปลี่ยนเป็นภาษาการเขียนโปรแกรม "ของจริง"

เมื่อทราบawkแล้ว(และเป็นคำตอบแล้ว) ฉันจึงเขียนการติดตั้งใช้งานในภาษาต่าง ๆ และทำการเปรียบเทียบกับชุดข้อมูล 10,000 บรรทัดเดียวกันบน PCI-E SSD

me* (C)                0m1.734s
me (C++)               0m1.991s
me (Python/Pypy)       0m2.390s
me (perl)              0m3.024s
Thor+Glenn (sed|sh)    0m3.353s
me (python)            0m3.359s
jasonwryan+Thor (awk)  0m3.779s
rush (while read)      0m6.011s
Thor (sed)             1m30.947s
me (parallel)          4m9.429s

ทันทีที่ C ดูดีที่สุด แต่มันก็เป็นหมูที่ต้องวิ่งให้เร็ว Pypy และ C ++ นั้นง่ายต่อการเขียนและทำงานได้ดีพอเว้นแต่ว่าคุณกำลังพูดถึงหลายพันล้านบรรทัด หากเป็นเช่นนั้นการอัพเกรดเพื่อทำสิ่งนี้ทั้งหมดใน RAM หรือ SSD อาจเป็นการลงทุนที่ดีกว่าการปรับปรุงรหัส

เห็นได้ชัดว่าในเวลาที่ฉันได้ใช้เวลาจะผ่านเหล่านี้คุณอาจอาจจะมีการประมวลผลไม่กี่ร้อยล้านระเบียนในตัวเลือกที่ช้าที่สุด หากคุณสามารถเขียนawkหรือ Bash ลูปเพียงทำเช่นนั้นและรับกับชีวิต วันนี้ฉันมีเวลาว่างมากเกินไปอย่างชัดเจน

ฉันยังทดสอบตัวเลือกแบบมัลติเธรดบางตัว (ใน C ++ และ Python และ hybrids ด้วย GNU parallel) แต่ค่าใช้จ่ายของเธรดนั้นมีมากกว่าประโยชน์ทั้งหมดสำหรับการดำเนินการอย่างง่าย (การแยกสตริงการเขียน)

Perl

awk( gawkที่นี่) โดยสุจริตจะเป็นพอร์ตแรกของฉันที่เรียกว่าการทดสอบข้อมูลเช่นนี้ แต่คุณสามารถทำสิ่งที่ค่อนข้างคล้ายกันใน Perl ไวยากรณ์ที่คล้ายกัน แต่มีจุดจับการเขียนที่ดีขึ้นเล็กน้อย

perl -ane 'open(my $fh, ">", $F[0].".seq"); print $fh $F[1]; close $fh;' infile

หลาม

ฉันชอบงูใหญ่ เป็นภาษาของงานประจำวันของฉันและเป็นเพียงภาษาที่ดีแข็งแกร่งและอ่านง่าย แม้แต่ผู้เริ่มต้นก็อาจเดาได้ว่าเกิดอะไรขึ้นที่นี่

with open("infile", "r") as f:
    for line in f:
        id, chunk = line.split()
        with open(id + ".seq", "w") as fw:
            fw.write(chunk)

คุณต้องจำไว้ว่าpythonไบนารีของการแจกแจงไม่ใช่การใช้ Python เพียงอย่างเดียว เมื่อฉันรันการทดสอบเดียวกันผ่าน Pypy มันเร็วกว่า Cโดยไม่มีการเพิ่มประสิทธิภาพตรรกะใด ๆ เพิ่มเติม โปรดทราบว่าก่อนที่จะเขียน Python เป็น "ภาษาช้า"

ฉันเริ่มตัวอย่างนี้เพื่อดูว่าเราสามารถทำให้ซีพียูของฉันทำอะไรได้จริงๆ แต่ตรงไปตรงมา C คือฝันร้ายที่จะเขียนโค้ดถ้าคุณไม่ได้แตะมันมาเป็นเวลานาน นี่มีข้อเสียเพิ่มเติมของการถูก จำกัด อยู่ที่ 100-char บรรทัด แต่มันง่ายมากที่จะขยายมันฉันแค่ไม่ต้องการมัน

รุ่นเดิมของฉันช้ากว่า C ++ และ pypy แต่หลังจากที่บล็อกเกี่ยวกับเรื่องนี้ผมได้รับความช่วยเหลือจากจูเลียน Klode ตอนนี้เวอร์ชั่นนี้เร็วที่สุดเพราะบัฟเฟอร์ IO ที่ปรับแต่งแล้ว นอกจากนี้ยังเป็นจำนวนมากอีกต่อไปและอื่น ๆ ที่เกี่ยวข้องมากกว่าสิ่งอื่นใด

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

#define BUFLEN (8 * 1024)

int main(void) {
    FILE *fp;
    FILE *fpout;

    char line[100];
    char *id;
    char *token;
    char *buf = malloc(BUFLEN);

    fp = fopen("infile", "r");

    setvbuf ( fp , buf , _IOLBF, BUFLEN );
    while (fgets(line, 100, fp) != NULL) {
        id = strtok(line, "\t");
        token = strtok(NULL, "\t");

        char *fnout = malloc(strlen(id)+5);
        fnout = strcat(fnout, id);
        fnout = strcat(fnout, ".seq");

        fpout = fopen(fnout, "w");
        setvbuf ( fpout , NULL , _IONBF , 0 );
        fprintf(fpout, "%s", token);
        fclose(fpout);
    }
    fclose(fp);

    return 0;
}

C ++

ดำเนินการได้ดีและมากง่ายต่อการเขียนกว่าซีจริงคุณมีทุกประเภทของสิ่งที่จับมือของคุณ (โดยเฉพาะอย่างยิ่งเมื่อมันมาถึงสตริงและการป้อนข้อมูล) ทั้งหมดนั่นหมายความว่าคุณสามารถทำให้ตรรกะง่ายขึ้นได้ strtokใน C เป็นหมูเพราะมันประมวลผลสายอักขระทั้งหมดแล้วเราต้องดำเนินการจัดสรรหน่วยความจำที่น่าเบื่อทั้งหมด สิ่งนี้จะหมุนไปตามเส้นจนกว่าจะถึงแท็บและเราดึงกลุ่มออกตามที่เราต้องการ

#include <fstream>
#include <string>
using namespace std;

int main(void) {
    ifstream in("infile");
    ofstream out;
    string line;

    while(getline(in, line)) {
        string::size_type tab = line.find('\t', 0);
        string filename = line.substr(0, tab) + ".seq";
        out.open(filename.c_str());
        out << line.substr(tab + 1);
        out.close();
    }

    in.close();
}

GNU Parallel

(ไม่ใช่รุ่นututils) มันเป็นไวยากรณ์ที่กระชับ แต่ OMGSLOW ฉันอาจจะใช้มันผิด

parallel --colsep '\t' echo {2} \> {1}.seq <infile

ทดสอบเครื่องกำเนิดไฟฟ้าเทียม

นี่คือเครื่องมือสร้างข้อมูลของฉันสำหรับ [100,000 ATGC] * 64 มันไม่เร็วและยินดีต้อนรับการปรับปรุงอย่างมาก

cat /dev/urandom | tr -dc 'ATGC' | fold -w 64 | awk 'NR>100000{exit}{printf NR"\t"$0"\n"}' > infile

2
ฉันควรชี้ให้เห็นว่าการแจกแจงทางเลือกทั้งหมดของคุณสำหรับการแสดงนั้นอาจสิ้นเปลืองเช่นเดียวกับสิ่งแรกที่เกิดขึ้นในใจ awkยังคงเป็นคำตอบที่ดีสำหรับสิ่งที่น้อยกว่าหลายสิบล้าน แม้ว่าคุณจะเพิ่มขนาดเป็นเส้นตรงเป็นพัน ๆ เส้น แต่ C สามารถช่วยคุณประหยัดเวลาได้มากกว่า Perl และ 1.5 ชั่วโมงถึง 3.6 ชั่วโมงเท่านั้น
Oli

ตอนนี้ฉัน c ++ รุ่นอยู่บนมีเพื่อให้ได้เร็วขึ้นมากบางทีฉันต้องการพิจารณา C ++ สำหรับการประมวลผลข้อความที่เรียบง่ายมากขึ้นของข้อมูลขนาดใหญ่ชุด มันเร็วเกือบสองเท่าและมันต่างกันหลายชั่วโมงเมื่อคุณเข้าสู่เส้นหลายพันล้านเส้น
Oli



1
ฉันคิดว่าความเร็วในการสร้างชุดทดสอบของคุณนั้นจะถูกกำหนดโดยตัวสร้างตัวเลขแบบสุ่ม paste <(yes A) <(yes T) <(yes G) <(yes C) | head -n1600000 | tr '\t' '\n' | shuf | tr -d \\n | fold -w64 | cat -n > infileคุณสามารถทำให้มันเร็วขึ้นโดยใช้หมายเลขโทรศัพท์ที่จะช่วยให้ทุกคนหรือสร้างการกระจายเป็นเนื้อเดียวกันเช่น:
ธ อร์

13

การใช้งานเชลล์บริสุทธิ์:

while read -r filename content ; do
    printf '%s\n' "$content" >> "${filename}.seq"
done < /source/file

12

การใช้awk:

awk '{printf "%s\n", $2>$1".seq"}' file

จากการเสนอชื่อfileให้พิมพ์ฟิลด์ที่สองในแต่ละเรคคอร์ด ( $2) ไปยังไฟล์ที่ตั้งชื่อตามฟิลด์แรก ( $1) โดย.seqต่อท้ายชื่อ

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

awk '{printf "%s\n", $2>$1".seq"; close($1".seq")}' file

สวัสดีวิธีนี้ใช้งานได้ดีมาก .. คุณช่วยอธิบายโค้ดหน่อยได้ไหม?
user3138373

@ user3138373 หวังว่าจะช่วย ...
jasonwryan

ช่วย .. ขอบคุณทำไมไม่พิมพ์งานแทน printf
user3138373

3
close($1".seq")หากมีหลายสายทั้งหมดอธิบายไฟล์ที่มีอยู่จะถูกนำมาใช้เพื่อให้คุณอาจจะเพิ่ม
Thor

1
@Thor จริง awkการใช้งานบางอย่างเช่น GNU รู้วิธีการแก้ไข
Stéphane Chazelas

3

นี่เป็นวิธีหนึ่งที่คุณสามารถทำได้ด้วย GNU sed:

<infile sed -r 's:(\w+)\s+(\w+):echo \2 > \1.seq:e; d'

หรือมีประสิทธิภาพมากขึ้นตามคำแนะนำของglenn jackman :

<infile sed -r 's:(\w+)\s+(\w+):echo \2 > \1.seq:' | sh

1
ในขณะที่เย็นมันค่อนข้างไม่มีประสิทธิภาพต้องวางไข่คำสั่งภายนอกสำหรับทุกบรรทัด มันจะดีกว่า
นิดหน่อยหากว่า

1
@glennjackman: นี่เป็นเพียงทางเลือกที่น่าสนใจในการทำ หากอินพุตมีขนาดใหญ่awkอาจเป็นเครื่องมือที่มีประสิทธิภาพที่สุดในการใช้ แน่นอนว่าคุณไม่มีสิทธิ์วางไข่shสำหรับแต่ละบรรทัดฉันได้เพิ่มตัวเลือกไปป์เป็นทางเลือก
Thor
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.