2012-05-23 18 views
29

Al momento ho un di 1 milione integers e controllo ogni integer contro una lista nera di 2000 integer s. Questo richiede circa 2 minuti.Qual è il modo più efficiente per confrontare l'elenco di numeri interi con l'elenco di numeri interi più piccolo?

for(int i = 0; i< MillionIntegerList.Length ; i++) 
{ 
    for(int blacklisted = 0; blacklisted < TwoThousandIntegerList.Length ; blacklisted++) 
     if(i==blacklisted) 
      i = 0; //Zero is a sentinel value 
} 

Questo rende 2.000.000.000 di iterazioni (loop) complessivamente. C'è un modo migliore di non vedere? grazie

+2

È una lista ordinata? –

+0

In realtà la lista dei milioni di interi è ordinata, la lista nera non è – theIrishUser

+0

Ma il costo di ordinare la lista nera nulla rispetto al costo di ciò che si vuole fare. – Thomash

risposta

51

Tre opzioni ora - le prime due sono più generali, in quanto non si basano su MillionIntegerList in fase di ordinamento (che non è stato originariamente specificato). Il terzo è preferibile nel caso in cui il grande elenco sia già ordinato.

Opzione 1

Sì, c'è sicuramente un modo migliore di farlo, utilizzando LINQ:

var common = MillionIntegerList.Intersect(TwoThousandIntegerList).ToList(); 

Che vi internamente utilizzare un HashSet<int> costruita attraverso il TwoThousandIntegerList, poi cercare ogni elemento di MillionIntegerList all'interno di esso - che sarà molto più efficiente di passare attraverso l'intero TwoThousandIntegerList ogni volta.

Se desideri solo quelli non-lista nera, è necessario:

var valid = MillionIntegerList.Except(TwoThousandIntegerList).ToList(); 

Nota che se avete solo bisogno di iterare i risultati una volta, è necessario rimuovere la chiamata ToList - ho incluso a materializzare i risultati in modo che possano essere esaminati più volte a buon mercato. Se stai semplicemente iterando, il valore di ritorno di Intersect o Except sarà solo stream i risultati, rendendolo molto più economico in termini di utilizzo della memoria.

Opzione 2

Se non si vuole fare affidamento sui dettagli di implementazione di LINQ to Objects, ma si vuole ancora un approccio basato su hash:

var hashSet = new HashSet<int>(TwoThousandIntegerList); 
hashSet.IntersectWith(MillionIntegerList); 
// Now use hashSet 

Opzione 3

L'approccio di utilizzare il fatto che la lista grande è ordinata sarebbe sicuramente utile.

Assumendo che non mente l'ordinamento della lista lista nera prima così, si potrebbe scrivere un flusso (e general purpose) implementazione come questo (non testata):

// Note: to use this, you'd need to make sure that *both* sequences are sorted. 
// You could either sort TwoThousandIntegerList in place, or use LINQ's OrderBy 
// method. 

public IEnumerable<T> SortedIntersect<T>(this IEnumerable<T> first, 
    IEnumerable<T> second) where T : IComparable<T> 
{ 
    using (var firstIterator = first.GetEnumerator()) 
    { 
     if (!firstIterator.MoveNext()) 
     { 
      yield break; 
     } 

     using (var secondIterator = second.GetEnumerator()) 
     { 
      if (!secondIterator.MoveNext()) 
      { 
       yield break; 
      } 
      T firstValue = firstIterator.Current; 
      T secondValue = secondIterator.Current; 

      while (true) 
      { 
       int comparison = firstValue.CompareTo(secondValue); 
       if (comparison == 0) // firstValue == secondValue 
       { 
        yield return firstValue; 
       } 
       else if (comparison < 0) // firstValue < secondValue 
       { 
        if (!firstIterator.MoveNext()) 
        { 
         yield break; 
        } 
        firstValue = firstIterator.Current; 
       } 
       else // firstValue > secondValue 
       { 
        if (!secondIterator.MoveNext()) 
        { 
         yield break; 
        } 
        secondValue = secondIterator.Current; 
       } 
      }     
     } 
    } 
} 

(si potrebbe prendere un IComparer<T> se voluto invece di fare affidamento sul fatto che T sia paragonabile.)

+0

grazie mille Jon Skeet – theIrishUser

+0

Presumendo MillionIntegerList ha molti più membri rispetto a TwoThousandIntegerList, sarebbe più veloce in questo modo? 'var common = TwoThousandIntegerList.Intersect (MillionIntegerList) .ToList();' –

+2

@ ErenErsönmez: No, perché ciò creerebbe un hashset del * più grande * set. La documentazione in MSDN è in realtà non corretta quando si tratta di Intersect - vedere http://msmvps.com/blogs/jon_skeet/archive/2010/12/30/reimplementing-linq-to-objects-part-16-intersect-and- build-fiddling.aspx per ulteriori informazioni. –

1

Utilizzare un hash per l'elenco bloccato.

foreach(integer i in MillionIntegerList) 
{ 
     //check if blockedlist contains i 
     //do what ever you like. 
} 
-2

uso Except metodo per la lista. Funzionerà

17

Poiché l'elenco grande è ordinato. Potresti ottenere i migliori risultati ordinando la piccola lista (molto veloce) e poi facendo una fusione lineare. Dovrai solo esaminare ogni elemento dell'elenco grande (e piccolo) una volta e non sarà necessario creare una Hashtable in background.

Vedere la parte merge function di MergeSort per un'idea su come eseguire questa operazione.

+0

Spero non ti dispiaccia: ho aggiunto un'implementazione generica di questa parte alla mia risposta. –

3

L'approccio richiede O (n * n) tempo. Considerate queste ottimizzazioni:

  • 1)

    Se i numeri interi non sono troppo grandi, è possibile utilizzare array di bool (per esempio se il più grande intero possibile è 1000000 uso bool [] b = new bool [ 1000000]). Ora per aggiungere un numero K alla lista nera usa b [K] = true. Il controllo è banale Questo funziona in O (n). È inoltre possibile utilizzare BitArray

  • 2)

    interi possono essere abbastanza grande. Utilizzare la struttura di ricerca binaria per l'archiviazione della lista nera (ad esempio SortedSet). ha O (logN) inserire e recuperare il tempo. Quindi in tutto è O (N * logN). La sintassi è lo stesso che per la List (Aggiungere (int K), Contiene (int K)), i duplicati vengono ignorati

+0

+1 per aver menzionato la ricerca binaria. - Se la lista più grande è già ordinata e i suoi elementi possono essere accessibili a basso costo dall'indice (come in un array) un algoritmo di ricerca binaria è quasi banale da implementare su di esso, riducendo il costo a O (n * log (m)) evitando copiare tutti i dati in un'altra struttura di dati come un albero o hashmap. – JimmyB

+0

Sì. se la "lista nera" non verrà modificata in futuro, può essere ordinata e gli elementi possono essere trovati utilizzando la ricerca binaria senza strutture aggiuntive come gli alberi. .NET ha già l'implementazione per l'ordinamento (Array.Sort e list.Sort) e la ricerca binaria (Array.BinarySearch e list.BinarySearch) –

1

Penso che la soluzione migliore è quella di utilizzare un Bloom filter e caso il filtro Bloom dice un elemento può essere nella lista nera, basta controllare se non è un falso positivo (che può essere fatto in O (Log (n)) se la lista nera è ordinata). Questa soluzione è efficiente nel tempo e non utilizza quasi nessuno spazio aggiuntivo che lo rende di gran lunga migliore rispetto all'utilizzo di un hashset.

Questa è la soluzione che Google utilizza per la lista nera in Chrome.

+3

Lasciatemi spiegare il mio downvote. 1) non risolve il problema (falsi positivi) 2) da nessuna parte si cita che è probabilistico e non risolve il problema attuale 3) Chrome in realtà utilizza il filtro solo per decidere di controllare un servizio web per la lista nera effettivamente. – ex0du5

+0

1) I falsi positivi sono molto rari quindi non dovrebbe essere un problema. – Thomash

+0

2) In effetti non ho spiegato tutto sul filtro Blomm, ma ritengo che chiunque possa cliccare sul link e leggere l'articolo di Wikipedia. – Thomash

1

Come fare una ricerca binaria nell'elenco più lungo, poiché è ordinata.

foreach(integer blacklisted in TwoThousandIntegerList) 
{ 
    integer i = MillionIntegerList.binarySearch(blacklisted) 
    if(i==blacklisted){ 
      //Do your stuff 
    } 
} 

Questa soluzione costa solo O (m log n) tempo, dove m è la dimensione della piccola lista e n è la dimensione della lista più lunga. Avvertenza: Questa soluzione presuppone che lo MillionIntegerList non contenga valori duplicati.

In caso contrario, è possibile ripetere le ripetizioni poiché devono trovarsi in un blocco contiguo. Per questo, assumerò che lo MillionInterList sia un elenco di record, ciascuno con uno value e uno index.

foreach(integer blacklisted in TwoThousandIntegerList) 
{ 
    integer index = MillionIntegerList.binarySearch(blacklisted) 
    //Find the index of the first occurrence of blacklisted value 
    while(index > 0 && MillionIntegerList[index - 1].value == blacklisted){ 
      --index; 
    } 
    while(MillionIntegerList[index].value == blacklisted){ 
      //Do your stuff 
      ++index; 
    } 
} 

Questa soluzione costa O (m log n + mk) dove k è il numero medio di duplicati per intero lista nera trovati nelle MillionInterList.

Problemi correlati