Metode terbaik untuk mengumpulkan sampel acak dari kumpulan file

23

Misalkan ada direktori yang menyimpan 300 file data. Saya ingin secara acak memilih 200 dari file-file itu dan memindahkannya ke direktori lain. Apakah ada cara untuk melakukan itu di bawah Unix / Linux?

pertanyaan kecil
sumber
R mungkin dapat melakukan ini dalam sekejap mata dengan list.files()...
sr
4
Saya samar-samar akan menyatu shufdan head(atau hanya menggunakan shuf -n, seharusnya membaca halaman manual ...)
Ulrich Schwarz

Jawaban:

32

Jika sistem Anda memiliki shuf, Anda dapat menggunakan ini dengan mudah (bahkan menangani nama file yang jelek):

shuf -zen200 source/* | xargs -0 mv -t dest

Jika Anda tidak memiliki shuftetapi memiliki sortyang dibutuhkan -R, ini harus bekerja:

find source -type f -print0 | sort -Rz | cut -d $'\0' -f-200 | xargs -0 mv -t dest
Kevin
sumber
7
Ah ya, karena di mana lagi orang akan mencari menyeret daripada di alat untuk menyortir. (Setidaknya shuftidak dipanggil troskarena melakukan kebalikan dari penyortiran.)
Ulrich Schwarz
2
Tidak ada yang namanya kebalikan dari penyortiran (dalam arti yang sama seperti tidak ada yang namanya "tidak ada cuaca"). Acak masih disortir, hanya disortir secara acak.
Plutor
1
Apa itu "-zen200"? Itu tidak ada dalam dokumentasi untuk shuf, atau di mana pun di Internet, tetapi contoh Anda tidak berfungsi tanpanya. Cukup mistis.
SigmaX
2
@SigmaX Memang, cukup zen, bukan. Petunjuk: 3 bendera terpisah.
Kevin
2
files=(*)
for (( i=0; i<200; i++ )); do
    keys=("${!files[@]}")
    rnd=$(( RANDOM % ${#keys[@]} ))
    key=${keys[$rnd]}
    mv "${files[$key]}" "$otherdir"
    unset files[$key]
done
glenn jackman
sumber
2

Masukkan semua nama file ke dalam array bernama "file" di bash:

files=( * )

ukuran array:

echo ${#files[@]}

tentukan 2/3 dari mereka sebagai ukuran sampel:

take=$((2*${#files[@]}/3)) 

for i in $(seq 1 $take)
do
    r=$((RANDOM%${#files[@]})) 
    echo ${files[r]}
done

Ini akan pilih duplikat, dan yang tidak diuji dengan nama file dengan kosong dan semacamnya.

Cara paling sederhana untuk menghindari duplikat adalah, untuk mengulangi semua file, dan memilih masing-masing dengan peluang 2/3, tetapi ini tidak akan menyebabkan 200 file.

Ini akan menghapus file jika dipilih dari daftar dan memenuhi persyaratan Anda:

#!/bin/bash
files=( * )
# define 2/3 of them as sample size:
take=$((2*${#files[@]}/3)) 

while (( i < $take ))
do
    r=$((RANDOM%${#files[@]})) 
    f=${files[r]}
    if [[ -n $f ]]
    then 
        i=$((i+1))    
        echo ${files[r]}
        unset files[r]    
    fi
done
Pengguna tidak diketahui
sumber
Anda mungkin memilih file yang sama lebih dari sekali.
glenn jackman
Script shell yang sangat bagus. Untuk mengatasi masalah Anda tidak mendapatkan 200 file, Anda mungkin ingin menggunakan Reservoir Sampling: en.wikipedia.org/wiki/Reservoir_sampling Saya akan menjadi lemah dan tidak menyertakan contoh skrip shell dari ini.
Bruce Ediger
@glennjackman: Saya menulis begitu, ya. Butuh beberapa menit untuk mencari tahu, cara menghapus entri dari array.
pengguna tidak diketahui
Peringatan kecil: $RANDOMhanya dapat memiliki nilai 0 hingga 32767, jadi ini tidak akan berfungsi dengan baik jika Anda memiliki lebih dari 32768 file. Juga, mengambil bias terhadap file pertama.
l0b0
@ l0b0: Persyaratan di mana, untuk memilih 200 dari 300. Jika file tidak ada di direktori saat ini, tetapi pada server file, itu tidak akan berfungsi juga. Persyaratan berbeda, jawaban berbeda.
pengguna tidak diketahui
2

Jika ini perlu acak secara statistik, Anda tidak boleh menggunakannya RANDOM % ${#keys[@]}. Mempertimbangkan:

  1. $RANDOM memiliki 32768 nilai unik
  2. Pilihan pertama adalah 1 dari 300 elemen
  3. 32768 = 109 * 300 + 68

Jadi, ketika memilih item pertama, ada peluang 110/32768 ~ = 0,33569% untuk masing-masing dari 68 elemen pertama, dan 109/32768 ~ = 0,33264% peluang untuk masing-masing dari 232 elemen lainnya untuk dipilih. Pemetikan diulang beberapa kali dengan peluang berbeda, tetapi bias terhadap elemen pertama setiap kali 32768 % ${#keys[@]} -ne 0, sehingga kesalahan bertambah.

Ini harus tidak bias , dan berfungsi dengan nama file apa pun:

while IFS= read -r -d '' -u 9
do
    mv -- "$REPLY" /target/dir
done 9< <(find /source/dir -mindepth 1 -print0 | shuf -n 200 -z)
l0b0
sumber
2

Solusi Kevin bekerja sangat baik! Hal lain yang sering saya gunakan karena merasa lebih mudah untuk mengingat dari atas kepala saya adalah sesuatu seperti:

cp `ls | shuf -n 200` destination
Callum C
sumber
0

Satu liner dalam bash:

ls original_directory/|sort -R|head -number_of_files_to_move|while read file; do cp "new_directory/"$file test; done
Tenang
sumber
Tolong jelaskan; U&L adalah basis pengetahuan.
countermode