Melhor método para coletar uma amostra aleatória de uma coleção de arquivos

23

Suponha que exista um diretório contendo 300 arquivos de dados. Quero selecionar aleatoriamente 200 desses arquivos e movê-los para outro diretório. Existe uma maneira de fazer isso no Unix / Linux?

bit-question
fonte
R provavelmente pode fazer isso em um piscar de um olho com list.files()...
sr_
4
Eu vagamente ligar juntos shufe head(ou o uso apenas shuf -n, deve ter lido a página man ...)
Ulrich Schwarz

Respostas:

32

Se o seu sistema possui shuf, você pode usá-lo de maneira bastante conveniente (mesmo lidando com nomes de arquivos feios):

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

Se você não tem, shufmas tem um sortque leva -R, isso deve funcionar:

find source -type f -print0 | sort -Rz | cut -d $'\0' -f-200 | xargs -0 mv -t dest
Kevin
fonte
7
Ah, sim, porque onde mais alguém procuraria embaralhar do que em uma ferramenta de classificação. (Pelo menos shufnão é chamado trosporque ele faz o oposto de classificação.)
Ulrich Schwarz
2
Não existe o oposto de classificar (no mesmo sentido que não existe "sem clima"). Random ainda está classificado, apenas classificado aleatoriamente.
Plutor
1
O que é o "-zen200"? Isso não está na documentação do shuf ou em qualquer lugar da Internet, mas seu exemplo não funciona sem ele. Bastante místico.
SigmaX
2
@SigmaX Na verdade, é bem zen, não é? Dica: são 3 bandeiras separadas.
21715 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
fonte
2

Coloque todos os nomes de arquivos em uma matriz chamada "arquivos" no bash:

files=( * )

tamanho da matriz:

echo ${#files[@]}

defina 2/3 deles como tamanho da amostra:

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

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

Isso selecionará duplicatas e não será testado com nomes de arquivos com espaços em branco e outros.

A maneira mais simples de evitar duplicatas é iterar sobre todos os arquivos e escolher cada um com chance de 2/3, mas isso não leva necessariamente a 200 arquivos.

Isso removerá um arquivo se ele tiver sido escolhido da lista e atender aos seus requisitos:

#!/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
Usuário desconhecido
fonte
Você pode selecionar o mesmo arquivo mais de uma vez.
Glenn Jackman
Script shell muito bom. Para contornar o problema de não obter 200 arquivos, você provavelmente deseja usar o Reservoir Sampling: en.wikipedia.org/wiki/Reservoir_sampling Vou ser fraco e não incluir um exemplo de script de shell.
11558 Bruce EdigerBom
@glennjackman: Eu escrevi, sim. Foram necessários alguns minutos para descobrir como remover entradas da matriz.
usuário desconhecido
Advertência secundária: $RANDOMpode ter apenas valores de 0 a 32767; portanto, isso não funcionará corretamente se você tiver mais de 32768 arquivos. Além disso, a busca é influenciada pelos primeiros arquivos.
L0b0
@ l0b0: Requisitos onde, para escolher 200 entre 300. Se os arquivos não estiverem no diretório atual, mas em um servidor de arquivos, ele também não funcionará. Requisitos diferentes, resposta diferente.
usuário desconhecido
2

Se isso precisar ser estatisticamente aleatório, você não deve usá-lo RANDOM % ${#keys[@]}. Considerar:

  1. $RANDOM tem 32768 valores únicos
  2. A primeira seleção é 1 de 300 elementos
  3. 32768 = 109 * 300 + 68

Portanto, ao selecionar o primeiro item, há uma chance de 110/32768 ~ = 0,333569% para cada um dos 68 primeiros elementos e 109/32768 ~ = 0,33264% de chance para cada um dos outros 232 elementos a serem selecionados. A escolha é repetida várias vezes com chances diferentes, mas inclinado para os primeiros elementos sempre que 32768 % ${#keys[@]} -ne 0, portanto, o erro se agrava.

Isso deve ser imparcial e funciona com qualquer nome de arquivo:

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
fonte
2

A solução de Kevin funciona muito bem! Outra coisa que eu usei muito, porque acho mais fácil lembrar de cima, é algo como:

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

Um forro no bash:

ls original_directory/|sort -R|head -number_of_files_to_move|while read file; do cp "new_directory/"$file test; done
Pacífica
fonte
Por favor elabore; U&L é uma base de conhecimento.
countermode