Ecco una soluzione semplice che effettua un solo passaggio per i file per campione. Se sai esattamente quanti oggetti verranno campionati dai file, probabilmente è ottimale.
Prima di tutto è la funzione di esempio. Questo utilizza lo stesso algoritmo a cui @NedBatchelder si è collegato in un commento su una risposta precedente (sebbene il codice Perl mostrato lì abbia selezionato solo una singola riga, piuttosto che diversi). Seleziona i valori da un iterable di linee e richiede solo che le righe attualmente selezionate vengano conservate in memoria in qualsiasi momento (più la successiva riga di candidati). Solleva uno ValueError
se l'iterabile ha meno valori della dimensione campionaria richiesta.
import random
def random_sample(n, items):
results = []
for i, v in enumerate(items):
r = random.randint(0, i)
if r < n:
if i < n:
results.insert(r, v) # add first n items in random order
else:
results[r] = v # at a decreasing rate, replace random items
if len(results) < n:
raise ValueError("Sample larger than population.")
return results
edit: In un'altra domanda, l'utente @DzinX ha notato che l'uso di insert
in questo codice rende la brutta prestazione (O(N^2)
) se siete il campionamento di un numero molto elevato di valori. La sua versione migliorata che evita tale problema è here. /edit
Ora abbiamo solo bisogno di fare un iterable adatto di articoli per la nostra funzione da cui provare. Ecco come lo farei usando un generatore. Questo codice manterrà un solo file alla volta e non richiede più di una riga alla volta. Il parametro opzionale exclude
, se presente, dovrebbe essere un set
contenente righe che sono state selezionate in una corsa precedente (e quindi non dovrebbe essere restituito).
import os
def lines_generator(base_folder, exclude = None):
for dirpath, dirs, files in os.walk(base_folder):
for filename in files:
if filename.endswith(".txt"):
fullPath = os.path.join(dirpath, filename)
with open(fullPath) as f:
for line in f:
cleanLine = line.strip()
if exclude is None or cleanLine not in exclude:
yield cleanLine
Ora, abbiamo solo bisogno di una funzione wrapper per legare questi due pezzi insieme (e gestire una serie di linee visto). Può restituire un singolo campione di dimensioni n
o un elenco di campioni count
, sfruttando il fatto che anche una porzione di un campione casuale è un campione casuale.
_seen = set()
def get_sample(n, count = None):
base_folder = r"C:\Tasks"
if count is None:
sample = random_sample(n, lines_generator(base_folder, _seen))
_seen.update(sample)
return sample
else:
sample = random_sample(count * n, lines_generator(base_folder, _seen))
_seen.update(sample)
return [sample[i * n:(i + 1) * n] for i in range(count)]
Ecco come può essere utilizzato:
def main():
s1 = get_sample(10)
print("Sample1:", *s1, sep="\n")
s2, s3 = get_sample(10,2) # get two samples with only one read of the files
print("\nSample2:", *s2, sep="\n")
print("\nSample3:", *s3, sep="\n")
s4 = get_sample(5000) # this will probably raise a ValueError!
Vuoi 10 linee casuali da ogni file o 10 linee _IN total_? –
Grazie, 10 righe casuali in totale. – user1582596
Le linee in questi file sono uniche? Ti aspetti linee/file da aggiungere tra le esecuzioni? Questi file contengono decine o milioni di righe? –