Zeichnen Sie zufällig eine bestimmte Anzahl von Linien aus einer Datendatei

13

Ich habe eine Datenliste, wie

12345
23456
67891
-20000
200
600
20
...

Angenommen, die Größe dieses Datensatzes (dh der Dateizeilen) ist N. Ich möchte zufällig mLinien aus dieser Datendatei zeichnen . Daher sollte die Ausgabe aus zwei Dateien bestehen. Eine Datei enthält diese mDatenzeilen und die andere enthält N-mDatenzeilen.

Gibt es eine Möglichkeit, dies mit einem Linux-Befehl zu tun?

user288609
quelle
1
Sind Sie besorgt über die Reihenfolge der Zeilen? z.B. Möchten Sie die Quellreihenfolge beibehalten oder möchten Sie, dass die Reihenfolge selbst zufällig ist und die Auswahl der Zeilen zufällig ist?
Peter.O

Antworten:

18

Dies ist möglicherweise nicht der effizienteste Weg, funktioniert aber:

shuf <file> > tmp
head -n $m tmp > out1
tail -n +$(( m + 1 )) tmp > out2

Mit $menthält die Anzahl der Zeilen.

Rob Wouters
quelle
@userunknown, sort -Rkümmert sich um die Zufälligkeit. Ich bin mir nicht sicher, ob Sie die Antwort dafür abgelehnt haben, aber schlagen Sie sie zuerst in der Manpage nach.
Rob Wouters
2
Beachten Sie, dass sort -Rdie Eingabe nicht genau zufällig sortiert wird: Sie gruppiert identische Zeilen. Also , wenn die Eingabe zB foo, foo, bar, barund m = 2, dann wird eine Datei enthalten beide foos und die anderen beiden enthalten bars. GNU Coreutils hat auch shuf, die die Eingabezeilen randomisiert. Außerdem benötigen Sie keine temporäre Datei .
Gilles 'SO- hör auf böse zu sein'
warum nicht shuf <file> |head -n $m?
Emanuele
@emanuele: Weil wir sowohl den Kopf als auch den Schwanz in zwei getrennten Dateien brauchen.
Rob Wouters
5

Dieses Bash / Awk-Skript wählt Zeilen nach dem Zufallsprinzip aus und behält die ursprüngliche Reihenfolge in beiden Ausgabedateien bei.

awk -v m=4 -v N=$(wc -l <file) -v out1=/tmp/out1 -v out2=/tmp/out2 \
 'BEGIN{ srand()
         do{ lnb = 1 + int(rand()*N)
             if ( !(lnb in R) ) {
                 R[lnb] = 1
                 ct++ }
         } while (ct<m)
  } { if (R[NR]==1) print > out1 
      else          print > out2       
  }' file
cat /tmp/out1
echo ========
cat /tmp/out2

Ausgabe, basierend auf den Daten in der Frage.

12345
23456
200
600
========
67891
-20000
20
Peter.O
quelle
4

Wie bei allen Unix-Dingen gibt es ein Hilfsprogramm dafür TM .

Programm des Tages: split
splitteilt eine Datei auf viele verschiedene Arten auf, -bBytes, -lZeilen, -nAnzahl der Ausgabedateien. Wir werden die -lOption nutzen. Da Sie nicht nur die ersten m, sondern zufällige Zeilen auswählen möchten , wird sortdie Datei zuerst in zufälliger Reihenfolge erstellt. Wenn Sie darüber lesen möchten sort, beziehen Sie sich auf meine Antwort hier .

Nun der eigentliche Code. Es ist eigentlich ganz einfach:

sort -R input_file | split -l $m output_prefix

Dadurch werden zwei Dateien mmit den N-mNamen output_prefixaaund erstellt , eine mit Zeilen und eine mit Zeilen output_prefixab. Stellen Sie sicher, dass mes sich um die größere Datei handelt, ansonsten erhalten Sie mehrere Dateien mit der gewünschten Längem (und eine mit N % m).

Wenn Sie sicherstellen möchten, dass Sie die richtige Größe verwenden, ist hier ein kleiner Code, um dies zu tun:

m=10 # size you want one file to be
N=$(wc -l input_file)
m=$(( m > N/2 ? m : N - m ))
sort -R input_file | split -l $m output_prefix

Edit: Es ist mir aufgefallen, dass einige sortImplementierungen kein -RFlag haben. Wenn Sie haben perl, können Sie ersetzen perl -e 'use List::Util qw/shuffle/; print shuffle <>;'.

Kevin
quelle
1
Leider sort -Rscheint das nur in einigen Versionen der Fall zu sein (wahrscheinlich die Gnu-Version). Für andere Plattformen habe ich ein Tool namens 'randline' geschrieben, das nichts anderes tut, als stdin zufällig zu machen. Es ist bei beesbuzz.biz/code für jeden, der es braucht. (Ich neige dazu, Dateiinhalt ziemlich viel zu mischen.)
flauschige
1
Beachten Sie, dass sort -Rdie Eingabe nicht genau nach dem Zufallsprinzip sortiert wird: Sie gruppiert identische Zeilen. Also , wenn die Eingabe zB foo, foo, bar, barund m = 2, dann wird eine Datei enthalten beide foos und die anderen beiden enthalten bars. GNU Coreutils hat auch shuf, die die Eingabezeilen randomisiert. Außerdem können Sie die Ausgabedateinamen , indem Sie wählen headund tailstattsplit .
Gilles 'SO - hör auf, böse zu sein'
4

Wenn es Ihnen nichts ausmacht, die Zeilen neu zu ordnen, und Sie GNU-Coreutils haben (dh auf nicht eingebettetem Linux oder Cygwin, das seit shufVersion 6.0 nicht zu alt ist ), shufordnet („shuffle“) die Zeilen einer Datei nach dem Zufallsprinzip neu. So können Sie die Datei mischen und die ersten m Zeilen in eine Datei und den Rest in eine andere Datei verschieben.

Es gibt keine ideale Möglichkeit, diesen Versand durchzuführen. Man kann nicht einfach verketten headund würde tailda headvorne puffern. Sie können verwenden split, aber Sie erhalten keine Flexibilität in Bezug auf die Ausgabedateinamen. Sie können awknatürlich verwenden:

<input shuf | awk -v m=$m '{ if (NR <= m) {print >"output1"} else {print} }'

Sie können verwenden sed, was für große Dateien unklar, aber möglicherweise schneller ist.

<input shuf | sed -e "1,${m} w output1" -e "1,${m} d" >output2

Oder Sie können verwenden tee, um die Daten zu duplizieren, wenn Ihre Plattform hat /dev/fd; das ist ok wenn m klein ist:

<input shuf | { tee /dev/fd/3 | head -n $m >output1; } 3>&1 | tail -n +$(($m+1)) >output2

Portabel können Sie awk verwenden, um jede Zeile der Reihe nach zu versenden. Beachten Sie, dass awk nicht sehr gut darin ist, seinen Zufallszahlengenerator zu initialisieren. Die Zufälligkeit ist nicht nur definitiv nicht für die Kryptographie geeignet, sondern auch nicht sehr gut für numerische Simulationen. Der Startwert ist für alle awk-Aufrufe auf jedem System in einem Zeitraum von einer Sekunde gleich.

<input awk -v N=$(wc -l <input) -v m=3 '
    BEGIN {srand()}
    {
        if (rand() * N < m) {--m; print >"output1"} else {print >"output2"}
        --N;
    }'

Wenn Sie eine bessere Zufälligkeit benötigen, können Sie dasselbe in Perl tun, in dem der RNG-Wert ordnungsgemäß festgelegt wird.

<input perl -e '
    open OUT1, ">", "output1" or die $!;
    open OUT2, ">", "output2" or die $!;
    my $N = `wc -l <input`;
    my $m = $ARGV[0];
    while (<STDIN>) {
        if (rand($N) < $m) { --$m; print OUT1 $_; } else { print OUT2 $_; }
        --$N;
    }
    close OUT1 or die $!;
    close OUT2 or die $!;
' 42
Gilles 'SO - hör auf böse zu sein'
quelle
@Gilles: Zum awkBeispiel: -v N=$(wc -l <file) -v m=4... und es wird nur eine "zufällige" Zeile $mgedruckt , wenn der Zufallswert kleiner als ist , anstatt $mzufällige Zeilen zu drucken ... Es scheint, perlals würde das mit rand dasselbe tun , aber ich ziehe an weiß nicht perlgut genug, um einen Kompilierungsfehler zu überwinden: Syntaxfehler in -e Zeile 7, in der Nähe von ") print"
Peter.O
@ Peter.O Danke, das kommt davon, dass man in einem Browser tippt und nachlässig bearbeitet. Ich habe den Awk- und Perl-Code korrigiert.
Gilles 'SO- hör auf böse zu sein'
Alle 3 Methoden funktionieren gut und schnell .. danke (+1) ... Ich komme langsam mit Perl klar ... und das ist eine besonders interessante und nützliche Aufteilung der Datei im shufBeispiel.
Peter.O
Ein Pufferproblem? . Vermisse ich etwas? Die head catKombination verursacht Datenverlust im folgenden zweiten Test 3-4 .... TEST 1-2 { for i in {00001..10000} ;do echo $i; done; } | { head -n 5000 >out1; cat >out2; } .. TEST 3-4 { for i in {00001..10000} ;do echo $i; done; } >input; cat input | { head -n 5000 >out3; cat >out4; } ... wc -lErgebnisse für die Ausgaben von TEST 1-2 sind 5000 5000 (gut), aber für TEST 3-4 sind 5000 4539 Größen beteiligt ... Hier ist ein Link zu meinem (nicht gut) .. die differnece variiert je nach Datei Testcode
Peter.O
@ Peter.O Schon wieder, danke. In der Tat headliest voraus; Was vorgelesen und nicht ausgedruckt wird, wird verworfen. Ich habe meine Antwort mit weniger eleganten, aber (da bin ich mir ziemlich sicher) richtigen Lösungen aktualisiert.
Gilles 'SO- hör auf böse zu sein'
2

Vorausgesetzt m = 7und N = 21:

cp ints ints.bak
for i in {1..7}
do
    rnd=$((RANDOM%(21-i)+1))
    # echo $rnd;  
    sed -n "${rnd}{p,q}" 10k.dat >> mlines 
    sed -i "${rnd}d" ints 
done

Hinweis: Wenn Sie durch 7eine Variable wie $1oder ersetzen $m, müssen Sie seqnicht die {from..to}-Notation verwenden, die keine Variablenerweiterung durchführt.

Es funktioniert, indem Zeile für Zeile aus der Datei gelöscht wird, die immer kürzer wird, sodass die Zeilennummer, die entfernt werden kann, immer kleiner werden muss.

Dies sollte nicht für längere Dateien und viele Zeilen verwendet werden, da für jede Zahl im Durchschnitt die halbe Datei für den 1. und die gesamte Datei für den 2. sed- Code gelesen werden muss.

Benutzer unbekannt
quelle
Er braucht eine Datei mit den Zeilen, die auch entfernt werden.
Rob Wouters
Ich dachte, "einschließlich dieser m Datenzeilen " sollte including themaber auch die ursprünglichen Zeilen bedeuten - daher includingnicht consisting ofund nicht benutzend only, aber ich denke, Ihre Interpretation ist, was user288609 bedeutete. Ich werde mein Skript entsprechend anpassen.
Benutzer unbekannt
Sieht gut aus. `` ``
Rob Wouters
@user unbekannt: Du hast das +1am falschen Ort. rnd=$((RANDOM%(N-i)+1))In Ihrem Beispiel sollte N = 21 sein. Es sedstürzt derzeit ab , wenn rndausgewertet wird 0. Außerdem ist die Skalierung bei all dem Neuschreiben der Datei nicht sehr gut. Beispiel: 123 Sekunden , um 5.000 zufällige Zeilen aus einer 10.000-Zeilen-Datei zu extrahieren, gegenüber 0,03 Sekunden für eine direktere Methode ...
Peter.O
@ Peter.O: Du hast recht (korrigiert) und du hast recht.
Benutzer unbekannt