Narysuj losowo określoną liczbę linii z pliku danych

13

Mam listę danych, na przykład

12345
23456
67891
-20000
200
600
20
...

Załóżmy, że rozmiar tego zestawu danych (tj. Wierszy pliku) wynosi N. Chcę losowo narysować mlinie z tego pliku danych. Dlatego dane wyjściowe powinny zawierać dwa pliki, jeden to plik zawierający te mwiersze danych, a drugi zawiera N-mwiersze danych.

Czy można to zrobić za pomocą komendy Linux?

użytkownik 288609
źródło
1
Czy martwi Cię kolejność linii? na przykład. Czy chcesz zachować porządek źródła, czy też chcesz, aby sekwencja była losowa, a wybór losowych linii?
Peter.O

Odpowiedzi:

18

To może nie być najbardziej efektywny sposób, ale działa:

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

Z $mzawierające liczbę wierszy.

Rob Wouters
źródło
@ userunknown, sort -Rdba o losowość. Nie jestem pewien, czy głosowałeś za odpowiedzią na to pytanie, ale najpierw sprawdź ją na stronie podręcznika.
Rob Wouters
2
Zauważ, że sort -Rnie sortuje dokładnie danych wejściowych losowo: grupuje identyczne linie. Tak więc, jeśli sygnał wejściowy jest na przykład foo, foo, bar, bara m = 2, wówczas jeden plik zawiera zarówno fooS, a drugi zawiera zarówno bars. Coreutils GNU również ma shuf, który losowo linie wejściowe. Ponadto nie potrzebujesz pliku tymczasowego .
Gilles „SO- przestań być zły”
dlaczego nie shuf <file> |head -n $m?
emanuele
@emanuele: Ponieważ potrzebujemy głowy i ogona w dwóch osobnych plikach.
Rob Wouters
5

Ten skrypt bash / awk wybiera linie losowo i zachowuje oryginalną sekwencję w obu plikach wyjściowych.

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

Dane wyjściowe na podstawie danych zawartych w pytaniu.

12345
23456
200
600
========
67891
-20000
20
Peter.O
źródło
4

Podobnie jak w przypadku wszystkich innych systemów uniksowych, istnieje narzędzie dla tej TM .

Program dnia: split
splitpodzieli plik na wiele różnych sposobów, -bbajtów, -llinii, -nliczby plików wyjściowych. Będziemy korzystać z tej -lopcji. Ponieważ chcesz wybrać losowe linie, a nie tylko pierwsze m, sortplik zostanie losowo wybrany jako pierwszy. Jeśli chcesz przeczytać o sort, zapoznaj się z moją odpowiedzią tutaj .

Teraz aktualny kod. To całkiem proste, naprawdę:

sort -R input_file | split -l $m output_prefix

To utworzy dwa pliki, jeden z mliniami i jeden z N-mliniami, o nazwach output_prefixaai output_prefixab. Upewnij się, mże potrzebujesz większego pliku, w przeciwnym razie otrzymasz kilka plików o długości m(i jeden z N % m).

Jeśli chcesz upewnić się, że używasz właściwego rozmiaru, oto mały kod, aby to zrobić:

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

Edycja: Zwróciłem uwagę, że niektóre sortimplementacje nie mają -Rflagi. Jeśli tak perl, możesz zastąpić perl -e 'use List::Util qw/shuffle/; print shuffle <>;'.

Kevin
źródło
1
Niestety sort -Rwydaje się , że jest tylko w niektórych wersjach (prawdopodobnie w wersji GNU). Dla innych platform napisałem narzędzie o nazwie „randline”, które nie robi nic poza losowaniem standardowego wejścia. Jest na beesbuzz.biz/code dla każdego, kto tego potrzebuje. (Często tasuję zawartość pliku.)
puszysty
1
Zauważ, że sort -Rnie sortuje dokładnie danych wejściowych losowo: grupuje identyczne linie. Tak więc, jeśli sygnał wejściowy jest na przykład foo, foo, bar, bara m = 2, wówczas jeden plik zawiera zarówno fooS, a drugi zawiera zarówno bars. Coreutils GNU również ma shuf, który losowo linie wejściowe. Ponadto można wybrać nazwy pliku wyjściowego poprzez wykorzystanie headi tailzamiastsplit .
Gilles „SO- przestań być zły”
4

Jeśli nie masz nic przeciwko zmianie kolejności wierszy i masz jądra GNU (tj. W niewbudowanym systemie Linux lub Cygwin, niezbyt stare od czasu shufpojawienia się w wersji 6.0), shuffunkcja „losowo” zmienia kolejność wierszy pliku losowo. Możesz więc przetasować plik i wysłać pierwsze m wierszy do jednego pliku, a resztę do innego.

Nie ma idealnego sposobu na wykonanie tej wysyłki. Nie możesz po prostu połączyć w łańcuch, heada tailponieważ headbuforowałby do przodu. Możesz użyć split, ale nie zyskujesz elastyczności w odniesieniu do nazw plików wyjściowych. Możesz awkoczywiście użyć :

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

Możesz użyć sed, co jest niejasne, ale być może szybsze w przypadku dużych plików.

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

Lub możesz użyć teedo skopiowania danych, jeśli Twoja platforma ma /dev/fd; to dobrze, jeśli m jest małe:

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

Przenośnie, możesz użyć awk, aby wywołać każdą linię po kolei. Zauważ, że awk nie jest zbyt dobry w inicjowaniu generatora liczb losowych; losowość nie tylko zdecydowanie nie nadaje się do kryptografii, ale nawet nie jest bardzo dobra do symulacji numerycznych. Ziarno będzie takie samo dla wszystkich wywołań awk w dowolnym systemie z okresem jednej sekundy.

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

Jeśli potrzebujesz lepszej losowości, możesz zrobić to samo w Perlu, który porządnie zasiewa RNG.

<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- przestań być zły”
źródło
@Gilles: Na awkprzykład: -v N=$(wc -l <file) -v m=4... i wypisuje „losową” linię, gdy losowa wartość jest mniejsza niż $m, zamiast wypisywać $mlosowe linie… Wygląda na to, że perlto samo robi z Rand , ale nie nie wiem perlwystarczająco dobrze, aby ominąć błąd kompilacji: błąd składni w -e wierszu 7, w pobliżu „) print”
Peter.O
@ Peter.O Dzięki, to wynika z pisania w przeglądarce i nieostrożnej edycji. Naprawiłem kod awk i perl.
Gilles 'SO - przestań być zły'
Wszystkie 3 metody działają dobrze i szybko ... dzięki (+1) ... Powoli zaczynam się zastanawiać nad perlem ... i to jest szczególnie interesujący i użyteczny podział plików w shufprzykładzie.
Peter.O
Problem buforowania? . Czy coś brakuje? head catCombo powoduje utraty danych w następujących drugim teście 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 -lWyniki dla wyjść TEST 1-25000 5000 (dobre), ale dla TEST 3-4 to 5000 4539 (źle). Różnice różnią się w zależności od rozmiaru plików ... Oto link do mojego kodu testowego
Peter.O
@ Peter.O Jeszcze raz, dziękuję. Rzeczywiście, headczyta z przodu; to, co czyta z wyprzedzeniem i nie drukuje, jest odrzucane. Zaktualizowałem odpowiedź o mniej eleganckie, ale (jestem dość pewny) prawidłowe rozwiązania.
Gilles „SO- przestań być zły”
2

Zakładając m = 7i 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

Uwaga: Jeśli zastąpisz 7zmienną taką jak $1lub $m, musisz użyć seq, a nie {from..to}adnotacji, która nie powoduje rozszerzenia zmiennych.

Działa poprzez usuwanie linia po linii z pliku, który staje się coraz krótszy, więc numer linii, który można usunąć, musi być coraz mniejszy.

Nie należy tego używać w przypadku dłuższych plików i wielu wierszy, ponieważ dla każdej liczby średnio połowa pliku musi zostać odczytana dla pierwszego, a cały plik dla drugiego kodu sed .

nieznany użytkownik
źródło
Potrzebuje pliku z usuniętymi liniami.
Rob Wouters
Pomyślałem, że „uwzględnienie tych m wierszy danych” powinno oznaczać, including themale również oryginalne wiersze - dlatego includingnie consisting ofi nie używam only, ale myślę, że twoja interpretacja jest tym, co użytkownik 288609 miał na myśli. Dostosuję odpowiednio mój skrypt.
użytkownik nieznany
Wygląda dobrze. ``
Rob Wouters
@ użytkownik nieznany: masz +1niewłaściwe miejsce. Powinno to być miejsce, w rnd=$((RANDOM%(N-i)+1))którym N = 21 w twoim przykładzie. Obecnie powoduje sedawarię, gdy rndjest oceniany na 0. .. Poza tym nie skaluje się zbyt dobrze przy całym tym ponownym zapisywaniu plików. np. 123 sekundy, aby wyodrębnić 5000 losowych linii z pliku 10.000 linii w porównaniu do 0,03 sekundy dla bardziej bezpośredniej metody ...
Peter.O
@ Peter.O: Masz rację (poprawiony) i masz rację.
użytkownik nieznany