2010-09-10 13 views
9

Ho un file di backup di file XML della libreria ITunes - circa 15 MB.La stringa esiste controllare 20 volte

Ho 20 file musicali sul mio drive C e circa 25K file su E drive sotto strutture di cartelle esattamente simili.

Sto attraversando la prima posizione e andando file per file e controllando se il file esiste nella seconda posizione. Quella parte funziona per me.

Ora, per tutti questi file duplicati, se il percorso del file dall'unità E esiste nell'XML, ma il percorso dell'unità C non esiste nell'XML, quindi voglio eliminare il file dall'unità C.

Qual è il modo migliore di verificare se esiste una stringa nel file XML (devo farlo almeno per 20 volte)?

+4

È necessario verificare solo che ciascuna stringa esista una volta oppure è necessario contare quante volte ciascuna si verifica? –

+6

Quante volte devi farlo? Una volta? Regolarmente? Dovrebbe essere veloce? 15 MB non è così tanto in questi giorni. – Kobi

+5

Quando dici "modo migliore", cosa significa "migliore"? Hai provato a caricarli in un 'HashSet ', e se sì cosa c'è di sbagliato nel farlo? – ChrisW

risposta

1

In ordine alfabetico, ordina l'elenco di stringhe su cui stai eseguendo l'abbinamento, quindi crea un array di indici che ti indica dove si trova l'inizio dell'elenco per ogni carattere che è un carattere di partenza per una delle stringhe, magari indicizzazione per secondo carattere in base all'ampiezza della varietà e se la tua corrispondenza è sensibile al maiuscolo o minuscolo.

Leggere il file carattere per carattere con uno stream per ridurre l'ingombro della memoria, controllando nell'array dell'indice per vedere dove quel personaggio inizia e finisce nell'elenco di stringhe in modo da poter estrarre quella pagina di caratteri, se c'è qualcosa che inizia con quelle combinazioni di caratteri. Quindi continua a filtrare all'interno della pagina finché non rimane una corrispondenza e il successivo fa corrispondenze 0.

Rimuovi quella stringa dall'elenco di stringhe corrispondenti, inseriscile in un'altra lista se lo desideri. Quindi inizia a controllare il tuo indice sul prossimo personaggio e continua a farlo ogni volta che non incontri nessuna corrispondenza.

L'indice fornisce un aggregato più efficiente per ridurre al minimo il numero di elementi iterati contro.

Questo potrebbe dare un due caratteri indice di profondità:

Dictionary<string,int> stringIndex = new Dictionary<char,int>(); 
for(int i = 0; i < sortedSearchStrings.Length; i++;) 
{ 
    if (!stringIndex.Keys.Contains(sortedSearchStrings[i][0])) stringIndex[sortedSearchStrings[i][0]] = i; 
    if (!stringIndex.Keys.Contains(sortedSearchStrings[i][0] + sortedSearchStrings[i][1])) stringIndex[sortedSearchStrings[i][0] + sortedSearchStrings[i][1]] = i; 
} 

quindi per individuare l'indice di partenza nella vostra lista è sufficiente accesso:

int startOfCurrentCharPage = stringIndex[string.Format("{0}{1}", lastChar, currentChar)]; 
+1

-1 per avere il tempo di scrivere/utilizzare un contenitore non standard senza la prova che è necessario. – ChrisW

+3

@ Chrishr: Seriamente? -1 per dare una risposta creativa? Scusate non ho appena detto di caricare tutto in memoria .. Bel lavoro dando -1 ad ogni risposta che non è la vostra .. –

+0

È creativo ma non sono d'accordo che sia buono. Il buono include la più semplice, la minima manutenzione, il minimo sforzo/spesa, il minimo debug, le prestazioni abbastanza buone, comprensibili al tipo nidificante, ecc. – ChrisW

3

A seconda se si desidera contare quante volte una stringa si verifica, o se si sta semplicemente verificando l'esistenza delle stringhe, l'approccio sarà leggermente diverso. Ma, questi sono i due modi vorrei prendere in considerazione di farlo:

Se si vuole farlo con il minimo:

Caricare il file riga per riga (o, se il vostro XML non è formattato come questo , nodo per nodo usando un parser XML ... Credo che ci siano parser XML che possono farlo). Esegui un'operazione di ricerca sulla linea per ogni stringa. Non più di una riga/nodo sarà in memoria alla volta, se si sovrascrive correttamente l'ultima riga. Il lato negativo di questo è che ci vorrà più tempo e il file sarà aperto più a lungo.

Se si vuole fare in fretta:

caricare l'intero file in memoria, non si preoccupano di analisi, e basta cercare per ogni stringa.

EDIT

Sulla base delle vostre precisazioni, vorrei prima di raccogliere tutti i nomi di file duplicati in un array, e quindi procedere alla scansione di ogni riga del file XML usando il mio primo metodo (sopra). Se si memorizzano già nomi di file 20K in memoria, esiterei a caricare l'intero XML da 15 MB allo stesso tempo.

+0

-1 per aver affermato che cercare tutta la memoria, ripetutamente, per ogni stringa sarà "veloce". – ChrisW

+0

@ Chrishr: -1 a voi per la scarsa comprensione della lettura. Ho detto, nel mio Edit, di caricare ogni nodo/linea uno per uno e scansionare ogni stringa nella riga. –

2

Un suggerimento: caricare come testo, utilizzare un'espressione regolare per estrarre le stringhe desiderate (suppongo che siano racchiuse da un tag specifico) e creare un elenco di hash con esse. È possibile utilizzare l'elenco per verificare l'esistenza.

+1

-1 per l'utilizzo dell'espressione regolare anziché dell'API XML per estrarre le stringhe. – ChrisW

+1

@ChrisW: la domanda non impone l'utilizzo dell'API XML. Inoltre, la domanda è stata modificata dopo la mia risposta. Nella domanda originale mi è stato detto che non era necessario leggere come XML. Quindi non sono d'accordo con il tuo -1 per la mia risposta. –

+0

È il suo avere un file XML come input che suggerisce l'utilizzo di una delle API XML incorporate. – ChrisW

0

leggere ogni stringa dal XML e scriverli in un HashSet<string>. Quando si desidera cercare una stringa, cercarla in HashSet. Il costo sarà O (n) per leggere l'XML, e O (n) per fare n ricerche dal HashSet. Non provare a cercare ripetutamente nell'XML (invece esegui le tue 20.000 ricerche nell'HashSet), perché l'XML non è indicizzato/ottimizzato per la ricerca.

1

Sarebbe possibile lavorare direttamente fuori dal documento xml e saltare il primo passaggio?

In tal caso, è possibile utilizzare Xml.XmlDocument e da lì Xml.XmlNode.SelectNodes (stringa), utilizzando xpath per spostarsi all'interno del documento. Non so che tipo di informazione sia presente nel documento, ma il modo in cui hai la seconda fase di parole dà l'idea che a volte sia il percorso su C: \ sia il percorso su E: \ sono presenti? Se è così, sarebbe semplice come due IO.File.Exists controlla e poi un IO.File.Delete().

Ciò che intendo dire è che anziché cercare il documento XML N volte per una stringa, effettuare la ricerca nel documento ed eliminare i duplicati mentre si procede, in modo da eseguire solo una volta il documento.

Io non uso iTunes o ho uno dei suoi backup di libreria a disposizione per dire se potrebbe funzionare o meno, però.

2

Ecco una soluzione semplice che utilizza Linq. Funziona sufficientemente veloce da essere utilizzato una sola volta:

using System; 
using System.IO; 
using System.Linq; 
using System.Xml.Linq; 

class ITunesChecker 
{ 
    static void Main(string[] args) 
    { 
     // retrieve file names 
     string baseFolder = @"E:\My Music\"; 
     string[] filesM4a = Directory.GetFiles(baseFolder, "*.m4a", SearchOption.AllDirectories); 
     string[] filesMp3 = Directory.GetFiles(baseFolder, "*.mp3", SearchOption.AllDirectories); 
     string[] files = new string[filesM4a.Length + filesMp3.Length]; 
     Array.Copy(filesM4a, 0, files, 0, filesM4a.Length); 
     Array.Copy(filesMp3, 0, files, filesM4a.Length, filesMp3.Length); 

     // convert to the format used by iTunes 
     for (int i = 0; i < files.Length; i++) 
     { 
      Uri uri = null; 
      if (Uri.TryCreate(files[i], UriKind.Absolute, out uri)) 
      { 
       files[i] = uri.AbsoluteUri.Replace("file:///", "file://localhost/"); 
      } 
     } 

     // read the files from iTunes library.xml 
     XDocument library = XDocument.Load(@"E:\My Music\iTunes\iTunes Music Library.xml"); 
     var q = from node in library.Document.Descendants("string") 
       where node.ElementsBeforeSelf("key").Where(n => n.Parent == node.Parent).Last().Value == "Location" 
       select node.Value; 

     // do the set operations you are interested in 
     var missingInLibrary = files.Except(q, StringComparer.InvariantCultureIgnoreCase); 
     var missingInFileSystem = q.Except(files, StringComparer.InvariantCultureIgnoreCase); 
     var presentInBoth = files.Intersect(q, StringComparer.InvariantCultureIgnoreCase); 
    } 
} 
Problemi correlati