2015-09-01 15 views
6

Ho un file con più di 5000 righe. Voglio trovare il modo più efficiente per scegliere una di queste righe ogni volta che eseguo il mio programma. Inizialmente avevo intenzione di usare il metodo casuale per sceglierne uno (era prima che sapessi che c'erano 5000 linee). Pensavo che potesse essere inefficiente quindi ho pensato di dare un'occhiata alla prima riga, quindi eliminarla dall'alto e aggiungerla in fondo. Ma sembra che devo leggere l'intero file e creare un nuovo file da eliminare dall'alto.Lettura di una riga casuale da un file di testo di grandi dimensioni

Qual è il modo più efficiente: il metodo casuale o il nuovo metodo di file?

Il programma sarà eseguito ogni 5 minuti e sto usando C# 4.5

+4

Cercare un offset casuale nel file, quindi eseguire la scansione in avanti per un carattere di nuova riga. Leggi i dati fino alla prossima nuova riga. Prendi le tue precauzioni con la fine del file. La probabilità non sarà uniforme se le linee hanno grandi differenze di lunghezza però. Oh, e 5000 non è * quello * molto ;-) –

+0

Spezzalo a 50 file con 100 linee ciascuno, num. Casuale 0-50 per file, riga casuale 0-99 per riga. Detto questo, leggere 5000 righe ogni 5 minuti non è ancora un grosso problema ... non efficiente, ma non un vero problema. Se questo è il tuo unico problema con l'app, sei buono :) – Noctis

+0

quanto è grande il file in totale? – olydis

risposta

0

presumo che l'obiettivo è quello di scegliere a caso una riga da un file di 5000+ linee.

Prova questo:

  1. ottenere il conteggio line utilizzando file.readlines (file) Count().
  2. Generare un numero casuale, utilizzando il conteggio delle righe come limite superiore.
  3. Eseguire una lettura lenta del file con File.ReadLines (file).
  4. Scegliere una linea da questa matrice utilizzando il numero casuale.

MODIFICA: come indicato, fare File.ReadLines (file) .toArray() è piuttosto inefficiente.

+1

Da tutte le cose suggerite nei commenti fino ad ora, questa sarebbe la soluzione più inefficiente. Entrambe le tue chiamate a 'File.ReadLines' leggeranno l'intero file (chiamare' ToArray' rende il passaggio 3 ** qualsiasi cosa che non sia pigro **) - a parte questo: sì, hai ragione, dal momento che questo è esattamente ciò che ha chiesto – olydis

+0

Oh , assolutamente giusto. Rimuoverò la chiamata al metodo ToArray(). Ma hai anche ragione nel dire che questo non è esattamente il metodo più efficiente in ogni caso. –

+1

Ancora si legge il file più di una volta: 'File.ReadAllLines' sarebbe più veloce, come in http://stackoverflow.com/questions/3745934/read-random-line-from-a-file-c-sharp – olydis

2

In .NET 4. *, è possibile accedere direttamente a una singola riga di un file. Ad esempio, per ottenere la linea X:

string line = File.ReadLines(FileName).Skip(X).First(); 

completa esempio:

var fileName = @"C:\text.txt" 
var file = File.ReadLines(fileName).ToList(); 
int count = file.Count(); 
Random rnd = new Random(); 
int skip = rnd.Next(0, count); 
string line = file.Skip(skip).First(); 
Console.WriteLine(line); 
+2

'File.ReadLines (FileName) .Skip (X) .Take (1) .First()' può essere semplificato in 'File.ReadLines (FileName) .Skip (X) .Prima()' – olydis

+0

Assolutamente, I Ho aggiornato la mia risposta. Grazie. – randoms

+0

L'intero esempio sta leggendo l'intero file in memoria due volte. – theB

0

Ecco una rapida implementazione di @LucasTrzesniewski s metodo proposto nei commenti alla domanda:

// open the file 
using(FileStream stream = File.OpenRead("yourfile.dat")) 
{ 
    // 1. index all offsets that are the beginning of a line 
    List<Long> lineOffsets = new List<Long>(); 
    lineOffsets.Add(stream.Position); //the very first offset is a beginning of a line! 
    int ch; 
    while((ch = stream.ReadByte()) != -1) // "-1" denotes the end of the file 
    { 
     if(ch == '\n') 
      lineOffsets.Add(stream.Position); 
    } 

    // 2. read a random line 
    stream.Seek(0, SeekOrigin.Begin); // go back to the beginning of the file 
    // set the position of the stream to one the previously saved offsets 
    stream.Position = lineOffsets[new Random().Next(lineOffsets.Count)]; 
    // read the whole line from the specified offset 
    using(StreamReader reader = new StreamReader(stream)) 
    { 
     Console.WriteLine(reader.ReadLine()); 
    } 
} 

I don Non ho alcun VS vicino a me al momento, quindi non è stato verificato.

+1

Potrebbe potenzialmente esplodere se si dispone di un file con caratteri multibyte come UTF-8 (che può richiedere 1-6 byte per carattere) e l'offset scelto casualmente si trova nel mezzo di uno di questi caratteri. –

1

Supponiamo che il file sia così grande che non ci si può permettere di inserirlo nella RAM. Poi, si vorrebbe utilizzare Reservoir Sampling, un algoritmo progettato per gestire la raccolta in modo casuale dalle liste di sconosciuti, lunghezza arbitraria che potrebbero non adattarsi in memoria:

Random r = new Random(); 
int currentLine = 1; 
string pick = null; 
foreach (string line in File.ReadLines(filename)) 
{ 
    if (r.Next(currentLine) == 0) { 
     pick = line; 
    } 
    ++currentLine; 
} 
return pick; 

A un livello elevato, il campionamento serbatoio segue una regola di base: ogni l'ulteriore linea ha una possibilità di 1/N di sostituire tutte le linee precedenti.

Questo algoritmo non è intuitivo. Ad un livello alto, funziona facendo in modo che la linea N abbia una possibilità 1/N di sostituire la linea attualmente selezionata. Quindi, la linea 1 ha una probabilità del 100% di essere selezionata, ma una possibilità del 50% di essere successivamente sostituita dalla linea 2.

Ho trovato la comprensione di questo algoritmo per essere più semplice sotto forma di una prova di correttezza. Quindi, una semplice dimostrazione per induzione:

1) Caso di base: tramite ispezione, l'algoritmo funziona se è presente 1 riga.
2) Se l'algoritmo funziona per le linee N-1, l'elaborazione delle linee N funziona perché:
3) Dopo l'elaborazione delle iterazioni N-1 di un file di linee N, tutte le linee N-1 sono ugualmente probabili (probabilità 1/(N -1)).
4) La successiva iterazione assicura che la linea N ha una probabilità di 1/N (perché è quello assegna esplicitamente l'algoritmo, ed è l'iterazione finale), riducendo la probabilità di tutte le linee precedenti:

1/(N-1) * (1-(1/N)) 
1/(N-1) * (N/N-(1/N)) 
1/(N-1) * (N-1)/N 
(1*(N-1))/(N*(N-1)) 
1/N 

Se si conosce il numero di righe presenti nel file in anticipo, questo algoritmo è più costoso del necessario, poiché legge sempre l'intero file.

+0

Vedere anche http://stackoverflow.com/questions/3745934/read-random-line-from-a-file-c-sharp – Duncan

+0

Sì, [risposta di tvanfosson] (http://stackoverflow.com/a/3745973/ 18192) è lo stesso. – Brian