200文字をランダムに抽出するスクリプトでshufを使用して代替なしでサンプリングする方法は?

200文字をランダムに抽出するスクリプトでshufを使用して代替なしでサンプリングする方法は?

任意の文字セットから200文字を抽出するスクリプトがあります。

#!/usr/bin/bash
n=$(stat -c "%s" newfile.txt)
r=$(shuf -i1-"$((n-200+1))" -n1)
< newfile.txt tail -c+"$r" | head -c200
for N in {1..10000}; do bash extraction_200pb.sh; done > output.txt 

私はshufそれが非常に強力であることを知っていますが、代わりにサンプルを含めたかったのです。つまり、サンプリングされた200文字ごとに選択される機会が一度だけあるという意味です。

出力は次のようになります。

>1     
GAACTCTACCAAAAGGTATGTTGCTTTCACAAAAAGCTGCATTCGATCATGTGTATAATCTAGCAAAACTAGTAGGAGGAGCAAAATACCCCGAAATTGTTGCTGCTCAGGCAATGCACGAATCAAACTACCTAGATCCTAGG
ACTAATAGTGTTTATAATGCCACAAATAGAACTAATGCTTTCGGTCAAACTGGTGAC
>2     
GCCTACCGCATAAAACAGCATCACCGCCACGGCTTCAGGGTATTCTCCAATGGCAAAGGCTCCCATGGTCGCGATGGACATTAAGAGAAATTCAGTAAAGAAATCTCCATTTAGAATACTTTTGAATCCTTCTTTTATCACCG
GAAAACCAACTGGGAGATAGGCCACAATGTACCAACCTACTCGCACCCAATCTGTAA
>3     
GCACGTGTCACCGTCAGCATCGCGGCAGCGGAACGGGTCACCCGGATTGCTGTCGGGACCATCGTTTACGCCGTCATTGTCGTTATCGGGATCGCCCGGATTACAAATGCCGTCGCCATCGACGTCGTTACCGTCGTTCGCGG
CATCGGGGAAGCCGGCACCGGCGGCACAGTCATCGCAACCGTCGCCATCGGCATCGA
>4     
GCGTTCGAAGCAATTGCACGAGACCCAAACAACGAATTGCTGGTTGTTGAACTGGAAAACTCTCTAGTCGGAATGCTTCAAATTACTTATATTCCCTACCTGACACATATTGGCAGTTGGCGTTGTCTTATAGAAGGTGTTCG
AATCCATAGTGACTATCGTGGACGAGGTTTTGGTGAGCAAATGTTCGCACATGCGAT
>5     
GTTTAAGACTAACAGCAATCTGTAAGGACATAGGTGCTGGAGTTGAGGTTAGTCTGGAAGATATGATCTGGGCAGAGAAATTGTCCAAAGCAAACACCGCAGCAAGAGGTATGCTAAACACAGCAAGAAGAATAAGTAATGAT
CCTACTGATTCTTTTCTGAATGAGTTGAATATAGGAGACCCCGACTCAACTCATCAT

入力ファイルは以下のように約8G程度のファイルです。

CCAAGATCGCTGGTTGGCGAATCAATTTCATAAACGCCTACGCTTTCAAGGAACGTGTTAAGAATGTTCT
GGCCGAGTTCCTTATGAGACGTTTCGCGTCCCTTAAATCGAATAACGACACGAACCTTGTCGCCGTCATT
AAGAAAACCCTTTGCCTTCTTGGCCTTAATCTGAATATCACGGGTGTCCGTTACAGGTCGCAACTGGATT
TCCTTGACTTCAGAAACAGACTTACGTGAATTCTTCTTGATTTCTTTCTGACGCTTTTCATTTTCATACT
GGAACTTGCCGTAATCAATGATCTTACAAACAGGAATATCACCCTTATCAGAGATCAATACCAAATCAAG
TTCGGCATCAAAAGCGCGATCAAGTGCGTCTTCAATGTCGAGGACCGTTGTTTCTTCACCGTCAACCAAA
CGAATTGTGGAGGACTTGATGTCGTCTCGGGTACTAATTTTATTCACGTATATGTTACTCCTTATGTTGT

どんな助けでも大変感謝します。よろしくお願いします。

答え1

これはawkでソリューションを実装したものです。データは8GBの擬似乱数16進数です(実際に16進変換マニュアルページ約12個、3300回繰り返し)。約1100万行あり、1行あたり平均725バイトです。

これはスケジュールされた実行です。

Paul--) ls -l tdHuge.txt
-rw-r--r-- 1 paul paul 8006529300 Dec 24 22:38 tdHuge.txt
Paul--) ./rndSelect
inFile ./tdHuge.txt; Size 8006529300; Count 10000; Lth 200; maxIter 50; Db 1;
Iteration   1 needs  10000
Iteration   2 needs   2712
Overlap   9561: 7663038508 to 7663038657
Iteration   3 needs    728
Iteration   4 needs    195
Iteration   5 needs     50
Iteration   6 needs     11
Iteration   7 needs      2
Required 7 iterations
Reporting 10000 samples

real    2m3.326s
user    0m3.496s
sys 0m10.340s
Paul--) wc Result.txt
  20000   20000 2068894 Result.txt
Paul--) head -n 8 Result.txt | cut -c 1-40
>1
5706C69636174656420696E666F726D6174696F6
>2
20737472696E672028696E207768696368206361
>3
20646F6573206E6F742067657420612068617264
>4
647320616E642073746F7265732E204966207468
Paul--) tail -n 8 Result.txt | cut -c 1-40
>9997
6F7374207369676E69666963616E7420646F7562
>9998
7472696E676F702D73747261746567793D616C67
>9999
865726520736F6D652066696C6573206D7573742
>10000
5726E65642E205768656E20746865202D66206F7
Paul--) 

ファイルをランダムに調べるため、繰り返しが必要です。プローブが隣接するプローブまたは改行文字と重なる場合、プローブは破棄され、小さなバッチの新しいプローブが作成されます。平均ライン長が725ラインで、サンプル要件が200ラインの場合、ほぼ30%のプローブが許容できないほどラインの端に近いです。実際のデータの平均行長が不明です。行が長いほど成功率が高くなります。

また、ヘッダー行(2020年12月4日の関連質問で説明されているように)がファイルにまだ存在するかどうかはわかりません。ただし、すべてのヘッダー行がサンプル長200未満の場合、ヘッダー行は削除されます(偶然に発生することをお勧めします)。

コードはほとんどGNU / awk(最小bash)であり、いくつかのコメントがあります。オプションでDb = 0を設定して非表示にできる残りのデバッグがたくさんあります。

#! /bin/bash

#.. Select random non-overlapping character groups from a file.

export LC_ALL="C"

#.. These are the optional values that will need to be edited.
#.. Command options could be used to set these from scripts arguments.

inFile="./tdHuge.txt"
outFile="./Result.txt"
Count=10000     #.. Number of samples.
Lth=200         #.. Length of each sample.
maxIter=50      #.. Prevents excessive attempts.

Size="$( stat -c "%s" "${inFile}" )"
Seed="$( date '+%N' )"
Db=1

#.. Extracts random non-overlapping character groups from a file.

Selector () {

    local Awk='
#.. Seed the random number generation, and show the args being used.
BEGIN {
    NIL = ""; NL = "\n"; SQ = "\047";
    srand (Seed % PROCINFO["pid"]);
    if (Db) printf ("inFile %s; Size %d; Count %d; Lth %d; maxIter %s; Db %s;\n",
        inFile, Size, Count, Lth, maxIter, Db);
    fmtCmd = "dd bs=%d count=1 if=%s iflag=skip_bytes skip=%d status=none";
}
#.. Constructs an array of random file offsets, replacing overlaps.
#.. Existing offsets are indexed from 1 to Count, deleting overlaps.
#.. Additions are indexed from -1 down to -N to avoid clashes.

function Offsets (N, Local, Iter, nSeek, Seek, Text, j) {

    while (N > 0 && Iter < maxIter) {
        ++Iter;
        if (Db) printf ("Iteration %3d needs %6d\n", Iter, N);

        for (j = 1; j <= N; ++j) {
            Seek[-j] = int ((Size - Lth) * rand());
            Text[Seek[-j]] = getSample( Seek[-j], Lth);
            if (Db7) printf ("Added %10d: \n", Seek[-j], Text[Seek[-j]]);
        }
        #.. Reindex in numeric order for overlap checking.
        nSeek = asort (Seek);
        if (Db7) for (j in Seek) printf ("%6d: %10d\n", j, Seek[j]);

        #.. Discard offsets that overlap the next selection.
        N = 0; for (j = 1; j < nSeek; ++j) {
            if (Seek[j] + Lth > Seek[j+1]) {
                if (Db) printf ("Overlap %6d: %10d to %10d\n",
                    j, Seek[j], Seek[j+1]);
                ++N; delete Text[Seek[j]]; delete Seek[j];
            } else if (length (Text[Seek[j]]) < Lth) {
                if (Db7) printf ("Short   %6d: %10d\n",
                    j, Seek[j]);
                ++N; delete Text[Seek[j]]; delete Seek[j];
            }
        }
    }
    if (Iter >= maxIter) {
        printf ("Failed with overlaps after %d iterations\n", Iter);
    } else {
        printf ("Required %d iterations\n", Iter);
        Samples( nSeek, Seek, Text);
    }
}
#.. Returns n bytes from the input file from position p.
function getSample (p, n, Local, cmd, tx) {

    cmd = sprintf (fmtCmd, n, SQ inFile SQ, p);
    if (Db7) printf ("cmd :%s:\n", cmd);
    cmd | getline tx; close (cmd);
    return (tx);
}
#.. Send samples to the output file.
function Samples (nSeek, Seek, Text, Local, j) {

    printf ("Reporting %d samples\n", nSeek);
    for (j = 1; j <= nSeek; ++j) {
        printf (">%d\n%s\n", j, Text[Seek[j]]) > outFile;
    }
    close (outFile);
}
END { Offsets( Count); }
'
    echo | awk -v Size="${Size}" -v inFile="${inFile}" \
        -v outFile="${outFile}" -v Count="${Count}" -v Lth="${Lth}" \
        -v maxIter="${maxIter}" \
        -v Db="${Db}" -v Seed="${Seed}" -f <( printf '%s' "${Awk}" )
}

#.. Test.

    time Selector

答え2

編集:他の答えと実装を追加します(修正が必要な場合があります)。現在の回答が部分的に置き換えられているため、すぐに削除できます。あるいは、実際の実装のための設計説明で更新することもできます。

アルゴリズムの基本フレームワークです。統計的に厳密にランダムな結果をエクスポートすることはできませんが、要件を満たすようです。私はこれをawkとしてプロトタイプすることもできます(Cliff RNGを使用するか、shufパイプから読むこともできます)。

  1. Nを配列Xに追加するオフセットの数(10000など)に設定します。

  2. N> 0の場合、2aから2dまで繰り返します。
    2a.Nブロックの任意のオフセットをXに追加します(既存のスクリプトの範囲を使用)。
    2b。 Xを数値の昇順で並べ替えます。
    2c。 Xを繰り返して、各オフセットを次のオフセットと比較し、後続のオフセットの200文字以内の位置を削除するようにマークします。
    2d。マークのオフセットを削除し、Nを削除回数に設定します。

  3. この時点で、私たちは10,000個の重複しないランダムオフセット(昇順)を持ちました。 (ステップ2が最終的に終了するのに十分な範囲がリーンであるかどうかをテストする必要があります(たとえば、少なくとも3 * 10000 * 200文字)。)
    3a。 10,000個のテールヘッドコマンドシーケンスを実行します(オフセットごとに1つずつ)。
    3b。 bashを介してこのコマンドをパイプします。

これを行うには、まずステップ2が終了するのに十分な範囲がないことを確認する必要があります(たとえば、少なくとも3 * 10000 * 200文字)。これには調査が必要な場合があります。

編集:サンプル間の重なりを確認するにはオフセットのみが必要ですが、行末との重なりを確認するにはデータが必要です。したがって、(2a)からサンプル自体を読み取り、最初のバイトのオフセットでインデックス付き配列に保存します。 (2c)で短いサンプルを確認して削除します。

これは、私たちがすでにすべての有効なサンプルをメモリに保存しているので、全体(3)を冗長にします(そしてそれをインデックスするための有効なオフセットのソートされたリストがあります)。したがって、すべてのサンプルをオフセット順に一覧表示できます。

関連情報