2013-06-17 16 views
5

Se ho due lista e vorrei sapere se ci sono almeno un elemento comune, ho questo due opzioni:intersecano e qualsiasi o contiene e qualsiasi. Qual è più efficiente trovare almeno un elemento comune?

lst1.Intersect(lst2).Any(); 

Lst1.Any(x => lst2.Contains(x)); 

Le due opzioni mi danno il risultato che mi aspetto, però non lo faccio sapere qual è l'opzione migliore. Quale è più efficiente? E perché?

Grazie.

EDIT: quando ho creato questo post, a parte la soluzione, stavo cercando il motivo. So che posso eseguire test, ma non saprei il motivo del risultato. Uno è più veloce dell'altro? È sempre una soluzione migliore dell'altra?

Quindi, per questo motivo, ho accettato la risposta di Matthew, non solo per il codice di test, ma anche per spiegare quando uno è migliore degli altri e perché. Apprezzo molto anche i contributi di Nicholas e Oren.

Grazie.

+2

Eseguire alcuni test o osservare l'IL generato? – zimdanen

+7

Da http://ericlippert.com/2012/12/17/performance-rant/ _Se hai due cavalli e vuoi sapere quale dei due è più veloce allora ** corri i tuoi cavalli ** ._ –

+1

Se uno sta andando decisamente meglio o peggio dell'altro, quindi la domanda di "Perché?" sarebbe valida. Ma senza sapere nulla dei tuoi dati, è impossibile rispondere. – Bobson

risposta

11

La risposta di Oren ha un errore nel modo in cui viene utilizzato il cronometro. Non viene ripristinato alla fine del ciclo dopo che è stato misurato il tempo impiegato da Any().

Nota come va di nuovo al punto di partenza del ciclo con il cronometro non essere mai Reset() in modo che il tempo che viene aggiunto al intersectinclude il tempo impiegato da Any().

Di seguito è una versione corretta.

Una build di rilascio correre fuori di qualsiasi debugger dà questo risultato sul mio PC:

Intersect: 1ms 
Any:  6743ms 

nota come sto facendo due liste non sovrapposti stringa per questo test. Si noti inoltre che questo è un test nel caso peggiore.

Dove ci sono molte intersezioni (o intersezioni che si verificano in prossimità dell'inizio dei dati) allora Oren è del tutto corretto per dire che Any() dovrebbe essere più veloce.

Se i dati reali contengono in genere intersezioni, è probabile che sia preferibile utilizzare Any(). Altrimenti, utilizzare Intersect(). È molto dipendente dai dati.

using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.Linq; 

namespace Demo 
{ 
    class Program 
    { 
     void run() 
     { 
      double intersect = 0; 
      double any = 0; 

      Stopwatch stopWatch = new Stopwatch(); 

      List<string> L1 = Enumerable.Range(0, 10000).Select(x => x.ToString()).ToList(); 
      List<string> L2 = Enumerable.Range(10000, 10000).Select(x => x.ToString()).ToList(); 

      for (int i = 0; i < 10; i++) 
      { 
       stopWatch.Restart(); 
       Intersect(L1, L2); 
       stopWatch.Stop(); 
       intersect += stopWatch.ElapsedMilliseconds; 

       stopWatch.Restart(); 
       Any(L1, L2); 
       stopWatch.Stop(); 
       any += stopWatch.ElapsedMilliseconds; 
      } 

      Console.WriteLine("Intersect: " + intersect + "ms"); 
      Console.WriteLine("Any: " + any + "ms"); 
     } 

     private static bool Any(List<string> lst1, List<string> lst2) 
     { 
      return lst1.Any(lst2.Contains); 
     } 

     private static bool Intersect(List<string> lst1, List<string> lst2) 
     { 
      return lst1.Intersect(lst2).Any(); 
     }    

     static void Main() 
     { 
      new Program().run(); 
     } 
    } 
} 

A fini comparativi, ho scritto il mio test di confronto tra int sequenze:

intersect took 00:00:00.0065928 
any took  00:00:08.6706195 

Il codice:

using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.Linq; 

namespace Demo 
{ 
    class Program 
    { 
     void run() 
     { 
      var lst1 = Enumerable.Range(0, 10000); 
      var lst2 = Enumerable.Range(10000, 10000); 

      int count = 10; 

      DemoUtil.Time(() => lst1.Intersect(lst2).Any(), "intersect", count); 
      DemoUtil.Time(() => lst1.Any(lst2.Contains),  "any",  count); 
     } 

     static void Main() 
     { 
      new Program().run(); 
     } 
    } 

    static class DemoUtil 
    { 
     public static void Print(this object self) 
     { 
      Console.WriteLine(self); 
     } 

     public static void Print(this string self) 
     { 
      Console.WriteLine(self); 
     } 

     public static void Print<T>(this IEnumerable<T> self) 
     { 
      foreach (var item in self) 
       Console.WriteLine(item); 
     } 

     public static void Time(Action action, string title, int count) 
     { 
      var sw = Stopwatch.StartNew(); 

      for (int i = 0; i < count; ++i) 
       action(); 

      (title + " took " + sw.Elapsed).Print(); 
     } 
    } 
} 

Se ho anche il tempo questo per sovrapposizione gamme cambiando il elenca questo e aumentando il count a 10000:

var lst1 = Enumerable.Range(10000, 10000); 
var lst2 = Enumerable.Range(10000, 10000); 

ottengo questi risultati:

intersect took 00:00:03.2607476 
any took 00:00:00.0019170 

In questo caso Any() è chiaramente molto più veloce.

Conclusione

la performance peggiore è molto male per Any() ma dimensioni adeguate per Intersect(). Le prestazioni migliori sono estremamente buone per Any() e non valide per Intersect(). (e caso migliore per Any() è probabilmente peggiore per Intersect()!)

Il Any() approccio è O (n^2) nel caso peggiore e O (1) nel migliore dei casi. L'approccio Intersect() è sempre O (N) (in quanto utilizza l'hashing, non l'ordinamento, altrimenti sarebbe O (N (log (N)))

Si deve anche considerare l'utilizzo della memoria:. Il metodo Intersect() esigenze di prendere una copia di uno degli ingressi, mentre Any() non lo fa.

quindi per prendere la decisione migliore si ha realmente bisogno di conoscere le caratteristiche dei dati reali, e in realtà eseguire test.

Se davvero non voglio che lo Any() diventi un O (N^2) nel peggiore dei casi, quindi dovresti usare Intersect(). Tuttavia, le e che ti sarà meglio usare Any().

E, naturalmente, il più delle volte niente di tutto ciò è importante!

A meno che non abbiate scoperto che questa parte del codice è un collo di bottiglia, questo è di mero interesse accademico. Non dovresti perdere tempo con questo tipo di analisi se non ci sono problemi. :)

+1

Nota che i dati di input hanno un impatto significativo sulle prestazioni qui. Alcuni tipi di dati risulteranno in uno essere più veloce mentre i dati diversi risulteranno nell'altro essere più veloce. È una questione di determinare quale è più probabile che sia il caso date le situazioni in cui si troverà l'OP. – Servy

+0

@Servy In effetti, stavo deliberatamente testando le prestazioni nel caso peggiore in cui non ci fossero intersezioni. –

+0

Una buona risposta a questa domanda potrebbe descrivere quali tipi di situazioni risulterebbero nelle prestazioni migliori/peggiori per ciascun algoritmo, nonché come ridimensiona i valori tra questi casi. Quindi il lettore può determinare dove è più probabile che i dati si adattino e utilizzare l'algoritmo appropriato. Qui 'Any' /' Contains' ha il caso più veloce, ma il caso peggiore più lento, e sarà generalmente più lento di 'Intersect' con un'ampia varietà di input, quindi' Intersect' è probabilmente il migliore a meno che tu non sappia molto probabili input di dati. – Servy

1

Vedere la risposta di Matthew per una ripartizione completa e accurata.

Relativamente facile da prendere in giro e provare voi stessi:

 bool found; 

     double intersect = 0; 
     double any = 0; 

     for (int i = 0; i < 100; i++) 
     { 
      List<string> L1 = GenerateNumberStrings(200000); 
      List<string> L2 = GenerateNumberStrings(60000); 
      Stopwatch stopWatch = new Stopwatch(); 

      stopWatch.Start(); 
      found = Intersect(L1, L2); 
      stopWatch.Stop(); 
      intersect += stopWatch.ElapsedMilliseconds; 

      stopWatch.Reset(); 

      stopWatch.Start(); 
      found = Any(L1, L2); 
      stopWatch.Stop(); 
      any += stopWatch.ElapsedMilliseconds; 
     } 

     Console.WriteLine("Intersect: " + intersect + "ms"); 
     Console.WriteLine("Any: " + any + "ms"); 
    } 

    private static bool Any(List<string> lst1, List<string> lst2) 
    { 
     return lst1.Any(x => lst2.Contains(x)); 
    } 

    private static bool Intersect(List<string> lst1, List<string> lst2) 
    { 
     return lst1.Intersect(lst2).Any(); 
    } 

Troverete che il metodo Any è significativamente più veloce nel lungo periodo, probabilmente perché non richiede le allocazioni di memoria e la configurazione richiede che si intersecano (Any arresta e restituisce true non appena trova una corrispondenza mentre Intersect ha effettivamente bisogno di memorizzare le partite in un nuovo List<T>).

+0

GenerateNumberStrings è un metodo di .NET o è un metodo implementato? –

+0

@Oren Sono d'accordo sul fatto che 'Any()' è probabilmente la scelta migliore, ma c'è un errore nel codice di test che deve essere corretto prima che i risultati siano significativi. –

+0

Buona presa, grazie per l'heads up, aggiornato per correttezza e riferito alla tua risposta. – Oren

3

Dipende dall'implementazione di IEnumerables.

Il tuo primo tentativo (Intersect/Any), trova tutte le corrispondenze e quindi determina se il set è vuoto o meno. Dalla documentazione, questo sembra essere qualcosa come O (n):

Quando l'oggetto restituito da questo metodo si enumera, Intersezione enumera prima, raccogliendo tutti elementi distinti di quella sequenza. Quindi enumera [il] in secondo luogo, contrassegnando quegli elementi che si verificano in entrambe le sequenze.Infine, gli elementi contrassegnati da vengono restituiti nell'ordine in cui sono stati raccolti.

tuo secondo tentativo (Any/Contains) enumera nel corso della prima collezione, un O (n), e per ogni elemento della prima collezione, enumera rispetto al secondo, un altro O (n), per vedere se viene trovato un elemento corrispondente Questo lo rende qualcosa come un'operazione O (n), vero? Quale pensi potrebbe essere più veloce?

Una cosa da considerare, però, è che la ricerca Contains() per certa collezione o forme indicate (ad esempio, dizionari, alberi binari o collezioni ordinate che permettono una ricerca binaria o lookup tabella hash) potrebbe essere un'operazione a buon mercato se l'Contains() implementazione è abbastanza intelligente da sfruttare la semantica della collezione su cui sta operando.

Ma è necessario sperimentare con i tipi di raccolta per scoprire quale funziona meglio.

Problemi correlati