Verileri daha hızlı sıralama yaklaşımı


11

Bir beddosyayı rastgele 10000 kez sıralamam ve her seferinde ilk 1000 satırı almam gerekiyor . Şu anda, aşağıdaki kodu kullanıyorum:

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

Her dosya için bunu yapmak yaklaşık 6 saat sürer. Çalışacak yaklaşık 150 tane var. Bunun için daha hızlı bir çözüm var mı?

Bir veri (myfile.bed_sorted) örnek:

    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
Dosyanız ne kadar büyük ve "rastgele" fikriniz ne kadar katı? spliterr, bir dosyayı her biri 1000 satırlık parçalara bölebilir, böylece tek bir çağrıda daha fazla dosya elde edebilirsiniz sort. Ayrıca, tüm dosyayı okuması gerekmediğinden headbiraz daha hızlı olup olmadığını kontrol ettiniz tailmi?
Ulrich Schwarz

@UlrichSchwarz: Yukarıda yapıştırdığım örnek dosya yaklaşık 33000 satır içeriyor. Genel olarak, tüm yatak dosyalarımda aşağı yukarı aynı sayıda satır bulunur. Ayrıca örneğin: 33000 satırlık bir dosyadan, 33 alt kümeyi (her birinde 1000 satır) tek bir çalıştırmada almak istemiyorum. Her koşuda sadece ilk 1000 satırı almak istiyorum. Aynı dosyanın kuyruğunu da yapacağım. Sadece örnek için headburada kullandım .
biobudhan

Man sayfasına göre sort -R"rastgele bir karma karması" kullanır. Karma oluşturmak tamamen zaman kaybıdır ve muhtemelen her şeyden daha uzun sürer. Satırları bir diziye okumak ve ardından dizinleri kullanarak karıştırmak daha iyi olur. Şahsen ben perlbunun için kullanırdım; bunu yapabilirsiniz bashancak rastgele sayılar üretmek için bir işleve ihtiyacınız vardır.
goldilocks

@goldilocks: Ben bir perlinsan değilim ! Lütfen bana yardım eder misin?
biobudhan

6
shufBunun yerine deneyin sort -R, oldukça hızlı. Tabii ki, bunu bellekte yapmak (bkz. Perl cevabı) kabuktaki tüm dosyayı yeniden okumak için gereken her şeyi yener.
frostschutz

Yanıtlar:


14

Dosyayı silmek için yeterli belleğe sahip olduğunuzu varsayarsak,

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

Bu 10000 kez yapmak istediğiniz için, tekrarı betiğe entegre etmenizi ve işleri hızlandırmak için dizinin kendisi yerine indeksleri karıştırmanızı tavsiye ederim :

$ 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

Yukarıda, her biri 37000 satır içeren bir dosyadan 1000 satırlık 10000 dosya oluşturuldu (örnek dosyanız 1000 kez tekrarlandı). Gördüğünüz gibi, sistemimde üç dakikadan biraz fazla zaman geçti.

açıklama

  • use List::Util 'shuffle';: Bu, shuffle()bir diziyi rasgele ayıran işlevi sağlayan bir Perl modülünü alır .
  • @l=<>;: giriş dosyasını ( <>) diziye yükleyin @l.
  • for $i (1..10000){} : 10000 kez çalıştır.
  • @r=shuffle(0..$#l);: $#lElemanların sayısı @lo kadar @rşimdi dizinin indeks numaralarının randomize listesi @l(giriş dosyanın çizgiler).
  • open(my $fh, ">","file.$i.bed");: file.$i.bedyazmak için çağrılan bir dosya açın . $i1 ile 10000 arasında değerler alır.
  • print $fh @l[@r[0..999]]: karıştırılan dizideki ilk 1000 dizini alın ve karşılık gelen satırları (öğelerinin @l) yazdırın .

Başka bir yaklaşım kullanmaktır shuf( teşekkürler @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

Vay!! Bu muhteşem!! 2 Dakikada çalıştı :-) Ben sadece bir sorum daha var. Dosyanın son 1000 satırını da almaya ne dersiniz? Bunu başarmak için dosyadaki uzunluğu (satır sayısı) bilmemiz gerekiyor mu? Lütfen yardım et!
biobudhan

1
@biobudhan düşünüyorsunuz shuffrostschutz önerdiği gibi: for i in {1..10000}; do shuf -n 1000 file.bed > file.$i.bed; done. Bu benim sistemimde ~ 1 dakika sürdü. Son 1000 satıra gelince, ihtiyacınız olan tek şey tail -n 1000.
terdon

1
@biobudhan ayrıca 3 kat daha hızlı perl sürümü için güncellenmiş cevaba bakınız.
terdon

Evet, denedim ve şimdi daha hızlı çalışıyor !! Çok teşekkür ederim!!! :-)
biobudhan

Perl sürümünün çıktı dosyalarını iki kez kontrol ettiniz mi? sysDosya I / O olacak kadar az zamana sahip olması bana tuhaf geliyor - bu shuf~ 30'lu olandan tamamen farklı olmamalıdır sys. Bu yüzden perl birini burada test ettim (kes ve yapıştır) ve O_O 1000 dosya oluşturdu ama tüm dosyalar boştu ...
goldilocks

9

Eğer, bu yapılabilir ne kadar hızlı görmek için bir kriter istiyorsanız yapıştırabilirsiniz kopyalayıp 10kshuffle.cppve derlemek g++ 10kshuffle.cpp -o 10kshuffle. Daha sonra çalıştırabilirsiniz:

10kshuffle filename < inputfile

filenameÇıktı dosyaları için kullanılacak temel yol nerede ; onlar şeklinde adlandırılır filename.0, filename.1vb ve her bir değişikliği yönünde ilk 1000 satır içeriyor. Her dosyanın adını giderken yazar.

#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;
}  

Tek bir 3.5 Ghz çekirdeğinde, bu ~ 20 saniye içinde çalışır:

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

data.txtsorudan 37000 satır kopyalandı. İlk 1000 satır yerine çıktı dosyasındaki tüm karışıklığı istiyorsanız, satır 54'ü şu şekilde değiştirin:

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

3

Bu nedenle, sorunuzun bir Unix yönü var, ancak önce temel sorununuzu çözmeye ve sonra bu çözümü uygulamak için bir Unix-y yolu bulmaya değer.

Bilinmeyen, çok sayıda satırı olan bir dosyadan her biri 1.000 büyüklüğünde 10.000 örnek oluşturmanız gerekir. Hafızada 10.000 x 1.000 satır tutabiliyorsanız, bunu dosyanın tek bir geçişinde yapmak mümkündür . Bellekte o kadar satır tutamazsanız, dosyanızda kaç satır bulunduğunu biliyorsanız bunu tek bir geçişte yapabilirsiniz. Dosyanızda kaç satır bulunduğunu bilmiyorsanız, satır sayısını saymak için bir ek geçişe ihtiyacınız vardır.

Daha zor durumda, satır sayısını bilmediğinizde algoritma, her örnek için aşağıdakileri yapmaktır (paralel olarak, örnekleri hafızada tutmak):

  • ilk 1000 satırı örneğe dahil et
  • n'inci satır için (burada n > 1000), olasılıkla birlikte ekleyin 1000 / nve önceden seçtiğiniz satırlardan rastgele bir satır atın. (bazı satırları atma olasılığı nedeniyle, girdinin sonuna kadar örneği hafızada tutmamız gerekir)

İkinci adımı uygulamak için bir zarif bir şekilde rasgele bir tam sayı üretmek için kde [1, n]. Eğer k <= 1000öyleyse satırı ekleyin ve varolan k-th satırı onunla değiştirin. İşte algoritmanın daha standart bir açıklaması: http://en.wikipedia.org/wiki/Reservoir_sampling

Satır sayısını biliyorsanız R, o zaman:

  • s0 örnek boyutuyla başla
  • olasılıkla n'inci satır ekleyin (1000 - s) / (R - n + 1)ve hemen çıktı alın (ve örnek boyutunu artırın s)

Unix'te bu nasıl yapılır? awkİnternette bu yazı başına cevap gibi görünüyor (doğruluğu için kefil olamam, ancak kod orada) https://news.ycombinator.com/item?id=4840043

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.