빠른 데이터 정렬 방법


11

bed파일을 무작위로 10000 번 정렬하고 매번 상위 1000 행을 가져와야합니다. 현재 다음 코드를 사용하고 있습니다.

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. 또한 전체 파일을 읽을 필요 headtail없기 때문에 보다 약간 빠른지 확인 했습니까?
Ulrich Schwarz 2016 년

@ UlrichSchwarz : 위에서 붙여 넣은 샘플 파일에는 약 33000 개의 행이 있습니다. 일반적으로 모든 침대 파일에는 거의 같은 수의 행이 있습니다. 또한 예를 들어 33000 행 파일에서 단일 실행으로 33 개의 하위 세트 (각각 1000 행)를 원하지 않습니다. 각 실행에서 상위 1000 행만 가져 가고 싶습니다. 나는 또한 같은 파일의 꼬리를 할 것입니다. 샘플 용으로 head여기에 사용 했습니다.
biobudhan 2016 년

매뉴얼 페이지에 따르면 sort -R"무작위 키 해시"를 사용합니다. 해시를 만드는 것은 총 시간 낭비이며 아마도 다른 것보다 오래 걸릴 것입니다. 행을 배열로 읽어서 색인을 사용하여 섞는 것이 좋습니다. 개인적으로 나는 그것을 사용할 것 perl입니다. 당신은 그것을 할 수 bash있지만 난수를 생성하는 함수가 필요합니다.
goldilocks 12

@goldilocks : 나는 perl사람이 아니다 ! 도와주세요.
biobudhan 2016 년

6
시도 shuf대신 sort -R이 상당히 빠릅니다. 물론 메모리에서 수행하면 (Perl 답변 참조) 셸에서 전체 파일을 다시 읽어야하는 모든 것을 이길 수 있습니다.
frostschutz

답변:


14

파일을 훔치기에 충분한 메모리가 있다고 가정하면 시도해 볼 수 있습니다

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

이 10000 번을하고 싶기 때문에 반복을 스크립트에 통합 하고 배열 자체 대신 인덱스섞어 속도를 높이는 것이 좋습니다.

$ 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

위의 37000 행을 포함하는 파일에서 1000 줄의 10000 파일을 만들었습니다 (예제 파일은 1000 번 반복되었습니다). 보시다시피, 시스템에서 3 분 이상이 걸렸습니다.

설명

  • use List::Util 'shuffle';: shuffle()배열을 무작위 화하는 기능 을 제공하는 Perl 모듈을 가져옵니다 .
  • @l=<>;: 입력 파일 ( <>)을 배열에 로드합니다 @l.
  • for $i (1..10000){} :이 10000 번 실행하십시오.
  • @r=shuffle(0..$#l);: $#l는 요소의 개수 @l이므로 @r이제 배열의 색인 번호 @l(입력 파일 행)의 무작위 목록입니다 .
  • open(my $fh, ">","file.$i.bed");: file.$i.bed쓰기를 위해 호출 된 파일을 엽니 다 . $i1에서 10000 사이의 값을 갖습니다.
  • print $fh @l[@r[0..999]]: 셔플 배열에서 처음 1000 개의 인덱스를 가져와 해당 행 (의 요소 @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 분 안에 일했습니다 :-) 한 가지 더 질문이 있습니다. 파일의 마지막 1000 줄을 검색하는 것은 어떻습니까? 이것을 달성하기 위해 파일의 길이 (줄 수)를 알아야합니까? 도와주세요!
biobudhan

1
@biobudhan은 frostschutz가 shuf제안한대로 고려 for i in {1..10000}; do shuf -n 1000 file.bed > file.$i.bed; done합니다. 내 시스템에서 약 1 분이 걸렸습니다. 마지막 1000 줄은 필요합니다 tail -n 1000.
terdon

1
@biobudhan은 또한 3 배 빠른 펄 버전에 대한 업데이트 된 답변을 참조하십시오.
terdon

예, 나는 그것을 시도했고 지금 더 빨리 작동합니다! 대단히 감사합니다 !!! :-)
biobudhan 2018 년

펄 버전의 출력 파일을 다시 확인 했습니까? sys파일 I / O가 될 시간 이 거의 없다는 것이 이상하게 보입니다 . shuf이것은 ~ 30s와 완전히 달라서는 안됩니다 sys. 내가 여기에 펄을 테스트 (컷 앤 페이스트) 등 O_O 가 1000 개 파일을 생성하지만, 모든 파일이 비어 있었다 ...
금발 미녀

9

당신이 그것을 할 수있는 방법을 빨리 볼 수있는 벤치 마크를 원하는 경우에이 붙여 복사 10kshuffle.cpp및 컴파일 g++ 10kshuffle.cpp -o 10kshuffle. 그런 다음 실행할 수 있습니다.

10kshuffle filename < inputfile

filename출력 파일에 사용할 기본 경로는 어디에 있습니까 ? 이름 filename.0filename.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.5Ghz 코어에서 ~ 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 줄이 복제되었습니다. 출력 파일에서 처음 1000 줄 대신 전체 셔플을하려면 54 행을 다음과 같이 변경하십시오.

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

3

따라서 귀하의 질문에 유닉스 측면이 있지만, 근본적인 문제를 먼저 해결 한 다음 해당 솔루션을 구현할 유닉스 방식을 찾는 것이 좋습니다.

알 수없는 많은 수의 행이있는 파일에서 각각 크기가 1,000 인 10,000 개의 샘플을 만들어야합니다. 메모리에 10,000 x 1,000 개의 행을 보유 할 수있는 경우 파일의 단일 패스 에서이 작업을 수행 할 수 있습니다. 메모리에 많은 행을 보유 할 수없는 경우 파일에 포함 된 행 수를 알고 있으면 한 번의 패스로 수행 할 수 있습니다. 파일에 포함 된 행 수를 모르는 경우 행 수를 세려면 하나의 추가 패스가 필요합니다.

행 수를 모르는 더 어려운 경우 알고리즘은 각 샘플에 대해 다음을 수행하는 것입니다 (병렬로 샘플을 메모리에 유지).

  • 샘플에 처음 1,000 행 포함
  • n 번째 행 (where n > 1000)의 경우 확률 1000 / n과 함께 포함하고 이미 선택한 행에서 임의의 행을 버립니다. (일부 행을 버릴 가능성 때문에 입력이 끝날 때까지 샘플을 메모리에 보관해야합니다)

두 번째 단계를 구현하는 우아한 방법은에서 임의의 정수를 생성하는 것 k입니다 [1, n]. 그런 k <= 1000다음 행을 포함시키고 기존 k행을 행으로 바꾸 십시오. 다음은 알고리즘에 대한보다 표준적인 설명입니다. http://en.wikipedia.org/wiki/Reservoir_sampling

행 수를 알고 있으면 R다음을 수행하십시오.

  • 표본 크기로 시작, s0
  • 확률로 n 번째 행을 포함 (1000 - s) / (R - n + 1)하고 즉시 출력하십시오 (샘플 크기를 늘리십시오 s)

유닉스에서 이것을하는 방법? awk인터넷 에서이 게시물에 대한 답변 인 것 같습니다 (정확성을 보증 할 수는 없지만 코드가 있습니다) https://news.ycombinator.com/item?id=4840043

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.