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?


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

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

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

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)

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

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

Tolong jelaskan; U&L adalah basis pengetahuan.
— countermode
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.