2009-04-17 10 views
20

Devo essere in grado di analizzare entrambi i file CSV e TSV. Non posso fare affidamento sugli utenti per conoscere la differenza, quindi vorrei evitare di chiedere all'utente di selezionare il tipo. C'è un modo semplice per rilevare quale delimitatore è in uso?Come dovrei rilevare quale delimitatore è usato in un file di testo?

Un modo sarebbe quello di leggere in ogni riga e contare entrambe le schede e le virgole e scoprire quale è il più utilizzato in ogni riga. Naturalmente, i dati potrebbero includere virgole o schede, in modo che sia più facile a dirsi che a farsi.

Modifica: Un altro aspetto divertente di questo progetto è che dovrò anche rilevare lo schema del file quando lo leggerò perché potrebbe essere uno dei tanti. Ciò significa che non saprò quanti campi ho fino a quando non posso analizzarlo.

risposta

14

È possibile mostrare loro i risultati nella finestra di anteprima, in modo simile a come fa Excel. È abbastanza chiaro quando viene utilizzato il delimitatore sbagliato in quel caso. Potresti quindi consentire loro di selezionare un intervallo di delimitatori e avere l'aggiornamento dell'anteprima in tempo reale.

Quindi è sufficiente effettuare una semplice ipotesi sul delimitatore per iniziare (ad esempio, una virgola o una scheda vengono prima).

+2

Mostrare all'utente il risultato prima dell'importazione è una buona mossa, penso, ma indovinare intelligentemente è ottimo anche per la nostra esperienza. Quindi la combinazione è davvero bella! – BerggreenDK

+0

un suggerimento-Se stai facendo una finestra di anteprima e vuoi "indovinare" quale è il delimitatore corretto, allora potresti dividere un possibile delim. e vedere se le prime dieci righe hanno tutti lo stesso numero di campi, confrontarle con tutte le altre delimitazioni normali. È una buona scommessa che si lavori con lo stesso numero di campi fino in fondo. Come [Jon Skeet ha detto] (https://stackoverflow.com/questions/761932/how-should-i-detect-which-delimiter-is-used-in-a-text-file/761949#761949) è perfettamente possibile è una scheda E delimitata da virgola delimitata da virgole, ma quella scheda era la scelta desiderata. – PsychoData

4

Sapete quanti campi devono essere presenti per riga?? Se è così, vorrei leggere le prime righe del file e controllare in base a quello.

Nella mia esperienza, i dati "normali" contengono spesso virgole ma raramente contengono caratteri di tabulazione. Ciò suggerirebbe di controllare un numero consistente di schede nelle prime righe e scegliere tale scelta come ipotesi preferita. Certo, dipende esattamente da quali dati hai.

In definitiva, sarebbe del tutto possibile avere un file che è completamente valido per entrambi i formati, quindi non è possibile renderlo assolutamente infallibile. Dovrà essere un lavoro "best effort".

1

Non esiste un modo "efficiente".

2

Immagino che la soluzione suggerita sarebbe la soluzione migliore. In un file CSV o TSV ben formato, il numero di virgole o tabulazioni rispettivamente per riga deve essere costante (nessuna variazione). Esegui il conteggio di ciascuno per ogni riga del file e controlla quale è costante per tutte le linee. Sembrerebbe abbastanza improbabile che il conteggio di entrambi i delimitatori per ogni riga sia identico, ma in questo caso inimmaginabilmente raro, si potrebbe ovviamente richiedere all'utente.

Se il numero di tabulazioni e virgole non è costante, visualizzare un messaggio all'utente che comunica che il file non è corretto ma il programma pensa che sia un file (indipendentemente dal formato con la deviazione standard minima dei delimitatori per riga) .

1

Supponendo che ci sia un numero fisso di campi per riga e che qualsiasi virgola o tabulazione all'interno di valori sia racchiusa tra virgolette ("), si dovrebbe essere in grado di elaborarla sulla frequenza di ciascun carattere in ogni riga. i campi non sono corretti, questo è più difficile, e se le virgolette non vengono usate per racchiudere caratteri delimitanti altrimenti, sarà, a mio avviso, quasi impossibile (e dipende dai dati specifici della locale)

1

In la mia esperienza, i dati raramente contiene schede, quindi una riga di campi delimitati da tabulazioni sarebbe (generalmente) abbastanza ovvia.

Le virgole sono più difficili, anche se - specialmente se stai leggendo dati in locali non statunitensi. n contengono un numero enorme di virgole se stai leggendo file generati fuori dal paese, poiché i numeri in virgola mobile spesso li contengono.

Alla fine, l'unica cosa sicura, però, è di solito da provare, quindi presentarlo all'utente e consentire loro di regolare, soprattutto se i dati conterranno virgole e/o schede.

1

Suppongo che nel testo normale le schede siano molto rare tranne che come primi caratteri su una riga - pensate a paragrafi rientrati o codice sorgente. Penso che se trovi le schede incorporate (ad esempio quelle che non seguono le virgole), puoi assumere che le schede vengano utilizzate come delimitatori e che siano corrette per la maggior parte del tempo. Questo è solo un sospetto, non verificato con alcuna ricerca. Ovviamente darei all'utente la possibilità di sovrascrivere la modalità calcolata automaticamente.

2

Basta leggere poche righe, contare il numero di virgole e il numero di schede e confrontarle. Se ci sono 20 virgole e nessuna tabulazione, è in CSV. Se ci sono 20 schede e 2 virgole (forse nei dati), è in TSV.

1

Supponendo di avere un set standard di colonne che si sta per aspettarsi ...

userei FileHelper (progetto open source su SourceForge). http://filehelpers.sourceforge.net/

Definire due modelli di lettore, uno per i comas, uno per le schede.

Se il primo fallisce, prova il secondo.

+0

Questo è interessante. Leggerò in più schemi e cercherò di capire quale schema il file corrente si basa sul layout del file (numero di campi, ordine dei campi, ecc.). FileHelper può determinare quale classe utilizzare in fase di runtime? – samiz

14

In Python, esiste una classe Sniffer nel modulo csv che può essere utilizzata per indovinare il delimitatore di un determinato file e citare i caratteri. La sua strategia è (citato da docstring di csv.py):


[In primo luogo, cercare] per il testo racchiuso tra due apici identici (la probabile quotechar) che sono preceduti e seguiti dallo stesso personaggio (la probabile delimitatore). Per esempio:

  ,'some text', 

La citazione con il maggior numero di vittorie, lo stesso con il delimitatore. Se non è presente alcuna quota, il delimitatore non può essere determinato in questo modo .

In tal caso, provare il seguente:

Il delimitatore dovrebbe verificarsi lo stesso numero di volte su ogni riga. Tuttavia, a causa di dati malformati, potrebbe non farlo. Non vogliamo che un approccio tutto o niente, quindi consentiamo piccole variazioni in questo numero .

  1. costruire una tabella della frequenza di ogni carattere su ogni riga.
  2. costruire una tabella di freqencies di questa frequenza (meta-frequenza?), Ad es. 'x è verificato 5 volte in 10 righe, 6 volte a 1000 righe, 7 volte in 2 righe'
  3. utilizzare la modalità del meta-frequenza per determinare l'atteso frequenza per tale carattere
  4. scoprire come spesso il carattere realtà incontra quell'obiettivo
  5. il personaggio che meglio soddisfa la sua obiettivo è il delimitatore

Per performan Per motivi, i dati vengono valutati in blocchi, quindi è possibile provare e valutare la porzione più piccola possibile dei dati, valutando pezzi aggiuntivi, se necessario.


Non ho intenzione di citare il codice sorgente qui - è nella directory Lib di ogni installazione Python.

Ricordate che il CSV può anche utilizzare il punto e virgola invece di virgole come delimitatori (ad esempio, nelle versioni tedesche di Excel, CSV sono punto e virgola delimitato da virgole, perché vengono utilizzati come separatori decimali in Germania ...)

3

E 'in PHP, ma questo sembra essere abbastanza affidabile:

$csv = 'something;something;something 
someotherthing;someotherthing;someotherthing 
'; 
$candidates = array(',', ';', "\t"); 
$csvlines = explode("\n", $csv); 
foreach ($candidates as $candidatekey => $candidate) { 
$lastcnt = 0; 
foreach ($csvlines as $csvline) { 
    if (strlen($csvline) <= 2) continue; 
    $thiscnt = substr_count($csvline, $candidate); 
    if (($thiscnt == 0) || ($thiscnt != $lastcnt) && ($lastcnt != 0)) { 
    unset($candidates[$candidatekey]); 
    break; 
    } 
    $lastcnt = $thiscnt; 
} 
} 
$delim = array_shift($candidates); 
echo $delim; 

ciò che fa è il seguente: per ogni possibile delimitatore specificato, legge ogni riga del CSV e controlla se il numero di volte in cui si verifica ogni separatore è costante. In caso contrario, il seperatore candidato viene rimosso e alla fine si dovrebbe finire con un separatore.

0

È possibile controllare se una linea sta usando un delimitatore o l'altro in questo modo:

while ((line = readFile.ReadLine()) != null) 
{ 
    if (line.Split('\t').Length > line.Split(',').Length) // tab delimited or comma delimited? 
     row = line.Split('\t'); 
    else 
     row = line.Split(','); 

    parsedData.Add(row); 
} 
+3

Cosa succede se è delimitato da tabulazioni con una serie di virgole nei dati o viceversa? Ciò potrebbe potenzialmente tentare di analizzare lo stesso file in entrambi i formati delimitato da tabulazioni o delimitato da virgole in base ai dati nelle righe. – samiz

2

mi sono imbattuto in una simile esigenza e ho pensato di condividere quello che mi è venuta. Non ho ancora eseguito molti dati attraverso di esso, quindi ci sono possibili casi limite. Inoltre, tieni presente che l'obiettivo di questa funzione non è la certezza del 100% del delimitatore, ma la migliore ipotesi da presentare all'utente.

/// <summary> 
/// Analyze the given lines of text and try to determine the correct delimiter used. If multiple 
/// candidate delimiters are found, the highest frequency delimiter will be returned. 
/// </summary> 
/// <example> 
/// string discoveredDelimiter = DetectDelimiter(dataLines, new char[] { '\t', '|', ',', ':', ';' }); 
/// </example> 
/// <param name="lines">Lines to inspect</param> 
/// <param name="delimiters">Delimiters to search for</param> 
/// <returns>The most probable delimiter by usage, or null if none found.</returns> 
public string DetectDelimiter(IEnumerable<string> lines, IEnumerable<char> delimiters) { 
    Dictionary<char, int> delimFrequency = new Dictionary<char, int>(); 

    // Setup our frequency tracker for given delimiters 
    delimiters.ToList().ForEach(curDelim => 
    delimFrequency.Add(curDelim, 0) 
); 

    // Get a total sum of all occurrences of each delimiter in the given lines 
    delimFrequency.ToList().ForEach(curDelim => 
    delimFrequency[curDelim.Key] = lines.Sum(line => line.Count(p => p == curDelim.Key)) 
); 

    // Find delimiters that have a frequency evenly divisible by the number of lines 
    // (correct & consistent usage) and order them by largest frequency 
    var possibleDelimiters = delimFrequency 
        .Where(f => f.Value > 0 && f.Value % lines.Count() == 0) 
        .OrderByDescending(f => f.Value) 
        .ToList(); 

    // If more than one possible delimiter found, return the most used one 
    if (possibleDelimiters.Any()) { 
    return possibleDelimiters.First().Key.ToString(); 
    } 
    else { 
    return null; 
    } 

} 
Problemi correlati