การเรียงลำดับข้อมูลเร็วขึ้น


11

ฉันต้องเรียงลำดับbedไฟล์แบบสุ่ม 10,000 ครั้งและรับ 1,000 แถวสูงสุดในแต่ละครั้ง ขณะนี้ฉันกำลังใช้รหัสต่อไปนี้:

for i in {1..100}; do
    for j in {1..100}; do
        sort -R myfile.bed_sorted | tail -n 1000 > myfile.bed.$i.$j.bed
    done
done

ใช้เวลาเกือบ 6 ชั่วโมงในการทำเช่นนี้สำหรับแต่ละไฟล์ ฉันมีประมาณ 150 คนที่จะออกกำลังกาย มีวิธีแก้ปัญหาที่เร็วกว่านี้หรือไม่?

ตัวอย่างของข้อมูล (myfile.bed_sorted) ฉันมี:

    chr1    111763899   111766405   peak1424    1000    .   3224.030    -1  -1
    chr1    144533459   144534584   peak1537    998 .   3219.260    -1  -1
    chr8    42149384    42151246    peak30658   998 .   3217.620    -1  -1
    chr2    70369299    70370655    peak16886   996 .   3211.600    -1  -1
    chr8    11348914    11352994    peak30334   990 .   3194.180    -1  -1
    chr21   26828820    26830352    peak19503   988 .   3187.820    -1  -1
    chr16   68789901    68791150    peak11894   988 .   3187.360    -1  -1
    chr6    11458964    11462245    peak26362   983 .   3169.750    -1  -1
    chr1    235113793   235117308   peak2894    982 .   3166.000    -1  -1
    chr6    16419968    16422194    peak26522   979 .   3158.520    -1  -1
    chr6    315344  321339  peak26159   978 .   3156.320    -1  -1
    chr1    111756584   111759633   peak1421    964 .   3110.520    -1  -1
    chrX    12995098    12997685    peak33121   961 .   3100.000    -1  -1
    chr9    37408601    37410262    peak32066   961 .   3100.000    -1  -1
    chr9    132648603   132651523   peak32810   961 .   3100.000    -1  -1
    chr8    146103178   146104943   peak31706   961 .   3100.000    -1  -1
    chr8    135611963   135614649   peak31592   961 .   3100.000    -1  -1
    chr8    128312253   128315935   peak31469   961 .   3100.000    -1  -1
    chr8    128221486   128223644   peak31465   961 .   3100.000    -1  -1
    chr8    101510621   101514237   peak31185   961 .   3100.000    -1  -1
    chr8    101504210   101508005   peak31184   961 .   3100.000    -1  -1
    chr7    8173062 8174642 peak28743   961 .   3100.000    -1  -1
    chr7    5563424 5570618 peak28669   961 .   3100.000    -1  -1
    chr7    55600455    55603724    peak29192   961 .   3100.000    -1  -1
    chr7    35767878    35770820    peak28976   961 .   3100.000    -1  -1
    chr7    28518260    28519837    peak28923   961 .   3100.000    -1  -1
    chr7    104652502   104654747   peak29684   961 .   3100.000    -1  -1
    chr6    6586316 6590136 peak26279   961 .   3100.000    -1  -1
    chr6    52362185    52364270    peak27366   961 .   3100.000    -1  -1
    chr6    407805  413348  peak26180   961 .   3100.000    -1  -1
    chr6    32936987    32941352    peak26978   961 .   3100.000    -1  -1
    chr6    226477  229964  peak26144   961 .   3100.000    -1  -1
    chr6    157017923   157020836   peak28371   961 .   3100.000    -1  -1
    chr6    137422769   137425128   peak28064   961 .   3100.000    -1  -1
    chr5    149789084   149793727   peak25705   961 .   3100.000    -1  -1
    chr5    149778033   149783125   peak25702   961 .   3100.000    -1  -1
    chr5    149183766   149185906   peak25695   961 .   3100.000    -1  -1

1
ไฟล์ของคุณมีขนาดใหญ่แค่ไหน splitสามารถผิดพลาด, แยกไฟล์ออกเป็นชิ้น 1000 สายแต่ละดังนั้นคุณจะได้รับไฟล์อื่น ๆ sortในสายเดียวของ นอกจากนี้คุณได้ตรวจสอบว่าheadเร็วกว่าเล็กน้อยtailเพราะไม่จำเป็นต้องอ่านไฟล์ทั้งหมดหรือไม่
Ulrich Schwarz

@UlrichSchwarz: ไฟล์ตัวอย่างที่ฉันได้วางด้านบนมีประมาณ 33000 แถว โดยทั่วไปไฟล์เตียงทั้งหมดของฉันจะมีจำนวนแถวมากขึ้นหรือน้อยลง ตัวอย่างเช่น: จากไฟล์แถว 33000 ฉันไม่ต้องการรับ 33 ชุดย่อย (1,000 แถวในแต่ละ) ในการทำงานครั้งเดียว ฉันเพียงต้องการที่จะรับ 1,000 แถวแรกจากการวิ่งแต่ละครั้ง ฉันจะทำไฟล์เดียวกันด้วย สำหรับตัวอย่างฉันใช้headที่นี่
biobudhan

ตามหน้า man sort -Rใช้ "hash สุ่มของกุญแจ" การสร้างแฮชเป็นการเสียเวลาโดยรวมและอาจใช้เวลานานกว่าสิ่งอื่นใด มันจะเป็นการดีกว่าถ้าคุณอ่านบรรทัดลงในอาร์เรย์แล้วสับเปลี่ยนโดยใช้ดัชนี โดยส่วนตัวฉันจะใช้perlเพื่อสิ่งนั้น คุณสามารถทำได้ด้วยbashแต่คุณจะต้องมีฟังก์ชันเพื่อสร้างตัวเลขสุ่ม
goldilocks

@goldilocks: ฉันไม่ใช่perlคน! คุณช่วยฉันหน่อยได้ไหม?
biobudhan

6
ลองshufแทนที่sort -Rมันเร็วกว่ามาก แน่นอนการทำในหน่วยความจำ (ดูคำตอบ Perl) จะเอาชนะสิ่งที่ต้องอ่านไฟล์ทั้งหมดในเปลือก
frostschutz

คำตอบ:


14

สมมติว่าคุณมีหน่วยความจำเพียงพอที่จะปัดไฟล์คุณสามารถลอง

perl -e 'use List::Util 'shuffle'; @k=shuffle(<>); print @k[0..999]' file.bed

เนื่องจากคุณต้องการทำสิ่งนี้ 10,000 ครั้งฉันขอแนะนำให้รวมการทำซ้ำลงในสคริปต์และสับดัชนีแทนอาร์เรย์เพื่อเพิ่มความเร็ว:

$ time perl -e 'use List::Util 'shuffle'; 
            @l=<>; for $i (1..10000){
               open(my $fh, ">","file.$i.bed"); 
               @r=shuffle(0..$#l); 
               print $fh @l[@r[0..999]]
            }' file.bed

real    1m12.444s
user    1m8.536s
sys     0m3.244s

ข้างต้นสร้าง 10,000 ไฟล์ของ 1,000 บรรทัดแต่ละไฟล์จากที่มี 37000 แถว (ไฟล์ตัวอย่างของคุณซ้ำ 1,000 ครั้ง) อย่างที่คุณเห็นมันใช้เวลามากกว่าสามนาทีในระบบของฉัน

คำอธิบาย

  • use List::Util 'shuffle';: นี้นำเข้าโมดูล Perl ที่ให้shuffle()ฟังก์ชั่นซึ่งสุ่มอาร์เรย์
  • @l=<>;: โหลดแฟ้มใส่ ( <>) @lลงในอาร์เรย์
  • for $i (1..10000){} : เรียกใช้นี้ 10,000 ครั้ง
  • @r=shuffle(0..$#l);: $#lคือจำนวนขององค์ประกอบใน@lเพื่อให้@rอยู่ในขณะนี้เป็นรายการแบบสุ่มของตัวเลขดัชนีของอาร์เรย์@l(เส้นแฟ้มใส่ของ)
  • open(my $fh, ">","file.$i.bed");: เปิดไฟล์ที่เรียกว่าfile.$i.bedการเขียน $iจะใช้ค่าตั้งแต่ 1 ถึง 10,000
  • print $fh @l[@r[0..999]]: ใช้ 1,000 ดัชนีแรกในอาร์เรย์ที่สับแล้วและพิมพ์บรรทัดที่เกี่ยวข้อง (องค์ประกอบของ@l)

อีกวิธีคือการใช้shuf( ขอบคุณ @frostschutz ):

$ time for i in {1..10000}; do shuf -n 1000 file.bed > file.$i.abed; done

real    1m9.743s
user    0m23.732s
sys     0m31.764s

ว้าว!! มันยอดเยี่ยมมาก !! ใช้งานได้ใน 2 นาที :-) ฉันมีคำถามอีกหนึ่งคำถาม ลองดึงไฟล์ 1,000 บรรทัดสุดท้ายหรือไม่ เพราะเราจำเป็นต้องทราบความยาว (จำนวนบรรทัด) ในไฟล์เพื่อให้ได้สิ่งนี้? กรุณาช่วย!
biobudhan

1
@biobudhan ไม่พิจารณาshufตามที่แนะนำโดย for i in {1..10000}; do shuf -n 1000 file.bed > file.$i.bed; donefrostschutz: ใช้เวลาประมาณ 1 นาทีในระบบของฉัน ในฐานะที่เป็นสำหรับที่ผ่านมา 1,000 tail -n 1000สายทั้งหมดที่คุณต้องมี
terdon

1
@biobudhan เห็นคำตอบที่ปรับปรุงแล้วสำหรับรุ่น Perl ที่เร็วขึ้น 3 เท่า
terdon

ใช่ฉันลองแล้วมันทำงานได้เร็วขึ้นแล้ว !! ขอบคุณมาก!!! :-)
biobudhan

คุณตรวจสอบไฟล์เอาต์พุตของเวอร์ชัน Perl ซ้ำสองครั้งหรือไม่? มันน่าแปลกที่ฉันว่ามันมีน้อยดังนั้นsysเวลาซึ่งจะเป็นไฟล์ I / O - นี้ไม่ควรจะเป็นอย่างนั้นแตกต่างกันโดยสิ้นเชิงกว่าshufหนึ่งซึ่งมี sys~ ดังนั้นผมทดสอบ Perl หนึ่งนี่ (ตัด n' วาง) และO_Oมันสร้าง 1000 ไฟล์ แต่ไฟล์ทั้งหมดที่ว่างเปล่า ...
Goldilocks

9

หากคุณต้องการมาตรฐานเพื่อดูวิธีการที่รวดเร็วก็สามารถทำได้คัดลอกวางลงในและรวบรวม10kshuffle.cpp g++ 10kshuffle.cpp -o 10kshuffleจากนั้นคุณสามารถเรียกใช้:

10kshuffle filename < inputfile

filenameเส้นทางพื้นฐานที่จะใช้สำหรับไฟล์ที่ส่งออกอยู่ที่ไหน พวกเขาจะได้รับการตั้งชื่อfilename.0, filename.1ฯลฯ และแต่ละคนมี 1000 บรรทัดแรกของสับ มันเขียนชื่อของแต่ละไฟล์ที่มันไป

#include <cerrno>
#include <cstdlib>
#include <cstring>
#include <fcntl.h>
#include <fstream>
#include <iostream>
#include <string>
#include <sstream>
#include <unistd.h>
#include <vector>

using namespace std;

unsigned int randomSeed () {
    int in = open("/dev/urandom", O_RDONLY);
    if (!in) {
        cerr << strerror(errno);
        exit(1);
    }
    unsigned int x;
    read(in, &x, sizeof(x));
    close(in);
    return x;
}

int main (int argc, const char *argv[]) {
    char basepath[1024];
    strcpy(basepath,argv[1]);
    char *pathend = &basepath[strlen(basepath)];
// Read in.
    vector<char*> data;
    data.reserve(1<<16);
    while (!cin.eof()) {
        char *buf = new char[1024];
        cin.getline(buf,1023);
        data.push_back(buf);
    }

    srand(randomSeed());
    for (int n = 0; n < 10000; n++) {
        vector<char*> copy(data);
    // Fisher-Yates shuffle.
        int last = copy.size() - 1;
        for (int i = last; i > 0; i--) {
            int r = rand() % i;
            if (r == i) continue;
            char *t = copy[i];
            copy[i] = copy[r];
            copy[r] = t;
        }
    // Write out.
        sprintf(pathend, ".%d", n);
        ofstream file(basepath);
        for (int j = 0; j < 1000; j++) file << copy[j] << endl;
        cout << basepath << endl;
        file.close();
    }

    return 0;
}  

บนแกน 3.5 Ghz เดียวสิ่งนี้จะทำงานภายใน 20 วินาที:

   time ./10kshuffle tmp/test < data.txt
   tmp/test.0
   [...]
   tmp/test.9999
   real 19.95, user 9.46, sys 9.86, RSS 39408

data.txtมี 37000 บรรทัดที่ทำซ้ำจากคำถาม หากคุณต้องการสลับทั้งหมดในไฟล์เอาต์พุตแทน 1,000 บรรทัดแรกให้เปลี่ยนบรรทัด 54 เป็น:

for (int j = 0; j < copy.size(); j++) file << copy[j] << endl; 

3

ดังนั้นคำถามของคุณจึงมีแง่มุมที่ Unix แต่ควรแก้ไขปัญหาพื้นฐานของคุณก่อนแล้วจึงพยายามหาวิธีที่ใช้ Unix-y เพื่อนำไปใช้แก้ปัญหานั้น

คุณต้องสร้าง 10,000 ตัวอย่างขนาด 1,000 แต่ละไฟล์จากแถวที่ไม่รู้จักจำนวนมาก เป็นไปได้ที่จะทำเช่นนี้ในไฟล์เดียวผ่านถ้าคุณสามารถเก็บ 10,000 x 1,000 แถวในหน่วยความจำ หากคุณไม่สามารถเก็บแถวนั้นไว้ในหน่วยความจำจำนวนมากคุณยังสามารถทำได้ในหนึ่งรอบหากคุณรู้ว่าไฟล์ของคุณมีจำนวนแถวเท่าใด หากคุณไม่ทราบจำนวนไฟล์ที่มีในไฟล์ของคุณคุณต้องมีหนึ่งรอบเพิ่มเติมเพื่อนับจำนวนแถว

อัลกอริทึมในกรณีที่ยากขึ้นเมื่อคุณไม่ทราบจำนวนแถวคือต้องทำสิ่งต่อไปนี้สำหรับแต่ละตัวอย่าง (ในแบบขนานโดยรักษาตัวอย่างในหน่วยความจำ):

  • รวม 1,000 แถวแรกในตัวอย่าง
  • สำหรับแถวที่ n (ที่n > 1000) ให้รวมกับความน่าจะเป็น1000 / nและยกเลิกแถวสุ่มจากแถวที่คุณเลือกไว้ (เนื่องจากความเป็นไปได้ในการทิ้งแถวเราจำเป็นต้องเก็บตัวอย่างไว้ในหน่วยความจำจนกระทั่งสิ้นสุดอินพุต)

วิธีที่สง่างามในการดำเนินการขั้นตอนที่สองคือการสร้างจำนวนเต็มสุ่มในk [1, n]หากk <= 1000รวมแถวและแทนที่kแถว -th ที่มีอยู่ด้วย นี่คือคำอธิบายอัลกอริทึมมาตรฐานเพิ่มเติม: http://en.wikipedia.org/wiki/Reservoir_sampling

หากคุณรู้จำนวนแถวRดังนั้น:

  • เริ่มต้นด้วยขนาดตัวอย่างเท่ากับs0
  • รวมแถวที่ n ด้วยความน่าจะเป็น(1000 - s) / (R - n + 1)และส่งออกทันที (และเพิ่มขนาดตัวอย่างs)

ทำอย่างไรกับ Unix awkน่าจะเป็นคำตอบต่อโพสต์นี้บนอินเทอร์เน็ต (ฉันไม่สามารถรับรองความถูกต้องของมันได้ แต่มีรหัส) https://news.ycombinator.com/item?id=4840043

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.