2009-08-27 12 views

risposta

21

più veloce come nel minor numero di linee-di-code:

var s = "nbHHkRvrXbvkn"; 
var duplicates = s.Where(ch => s.Count(c => c == ch) > 1); 
var result = new string(s.Except(duplicates).ToArray()); // = "RrX" 

più veloce come in più rapida delle prestazioni sarebbe probabilmente qualcosa di simile (non conserva l'ordine):

var h1 = new HashSet<char>(); 
var h2 = new HashSet<char>(); 

foreach (var ch in "nbHHkRvrXbvkn") 
{ 
    if (!h1.Add(ch)) 
    { 
     h2.Add(ch); 
    } 
} 

h1.ExceptWith(h2); // remove duplicates 

var chars = new char[h1.Count]; 
h1.CopyTo(chars); 
var result = new string(chars); // = "RrX" 

Performance test

In caso di dubbio - provarlo :)

 
Yuriy Faktorovich's answer  00:00:00.2360900 
Luke's answer      00:00:00.2225683 
My 'few lines' answer    00:00:00.5318395 
My 'fast' answer     00:00:00.1842144 
+1

Molto bello. Ottimo anche il confronto delle prestazioni. La variazione delle prestazioni è probabilmente ancora più visibile con stringhe molto grandi. – Alex

+1

Ho ripetuto il test delle prestazioni in Release build con il debugger staccato (ma la stessa stringa di input). Sono sorpreso dall'esecuzione della risposta di Yuriy; è abbastanza veloce! – dtb

+1

@dtb: La cosa che rallenta la mia risposta rispetto alla tua è che sto mantenendo l'ordine originale nella stringa di output, che richiede un ciclo aggiuntivo attraverso la stringa di input. La tecnica che io e te usiamo per trovare effettivamente i duplicati è * esattamente * la stessa. – LukeH

0

Questo algoritmo è generale, può essere applicato a qualsiasi lingua

  1. creare una mappa (HashTable) char-> int che contiene il conteggio di ogni carattere trovato, inizialmente vuoto
  2. scan the strin g una volta per popolare la mappa.
  3. creare una nuova stringa vuota che terrà l'output, potrebbe essere necessario utilizzare un oggetto StringBuilder.
  4. scansione stringa (orthe cartina, se minore) copiando solo caratteri un'occorrenza 1 alla stringa di output/StringBuilder
9

Qui è un ordine racchiudono abbastanza veloce. Ma sarei un po 'preoccupato di come LINQ fa Gruppo e Dove:

string s = "nbHHkRvrXbvkn"; 
Console.WriteLine( 
    s.ToCharArray() 
     .GroupBy(c => c) 
     .Where(g => g.Count() == 1) 
     .Aggregate(new StringBuilder(), (b, g) => b.Append(g.Key))); 

Edit: Questo batte Luca in alcuni casi ancora più lento di DTB di, ma conserva l'ordine

private static string MyMethod(string s) 
{ 
    StringBuilder sb = new StringBuilder(s.Length); 
    foreach (var g in s.ToCharArray().GroupBy(c => c)) 
     if (g.Count() == 1) sb.Append(g.Key); 

    return sb.ToString(); 
} 
+1

+1. Soluzione molto pulita. È anche incredibilmente veloce! – dtb

4

Questo uno dovrebbe essere piuttosto veloce (e conserva l'ordine originale):

public static string RemoveDuplicates(string source) 
{ 
    HashSet<char> found = new HashSet<char>(); 
    HashSet<char> dupes = new HashSet<char>(); 

    foreach (char c in source) 
    { 
     if (!found.Add(c)) 
     { 
      dupes.Add(c); 
     } 
    } 

    StringBuilder sb = new StringBuilder(source.Length); 
    foreach (char c in source) 
    { 
     if (!dupes.Contains(c)) 
     { 
      sb.Append(c); 
     } 
    } 
    return sb.ToString(); 
} 
+0

Perché pensi che la creazione di uno StringBuilder che è probabilmente troppo grande richiederà meno tempo di quello che consente di ottenere lo spazio al volo? –

+0

@Yuri: I benchmarked! Ho provato con milioni di stringhe casuali e il pre-ridimensionamento di 'StringBuilder' era più veloce nella maggior parte dei casi. Naturalmente, nel mondo reale le stringhe probabilmente non sarebbero puramente casuali. In tale situazione la differenza di prestazioni dipenderebbe dal rapporto tra dupes e non-dupes nella stringa di origine. – LukeH

+0

@Yuriy: ho appena eseguito il benchmark su una macchina diversa (Vista64 vs XP32) ei risultati erano molto più vicini. Sulla macchina a 64-bit sembra non fare alcuna differenza se il 'StringBuilder' è pre-allocato o meno. (Nel qual caso probabilmente ha senso non preoccuparsi di pre-allocare e risparmiare un po 'di RAM.) – LukeH

2

Ciò preserva l'ordine e, in base alle mie prove, è 4 volte più veloce rispetto all'utilizzo di un HashSet. Questo presuppone che il tuo range di caratteri sia 0-255 ma puoi estenderlo facilmente. Se si prevede di utilizzare questo in un ciclo, spostare il int[] c = new int[255]; fuori e nella funzione fare un Array.Clear(c,0,255).


     private static string RemoveDuplicates(string s) 
     { 
      int[] c = new int[255]; 
      for (int i = 0; i < s.Length; i++) 
      { 
       c[s[i]]++; 
      } 
      StringBuilder sb = new StringBuilder(); 
      for (int i = 0; i < s.Length; i++) 
      { 
       if (c[s[i]] == 1) sb.Append(s[i]); 
      } 
      return sb.ToString(); 
     } 
+0

Inoltre, non so se il compilatore srotolerà quei loop per te, ma puoi provare anche questo http: // it .wikipedia.org/wiki/Loop_unwinding – gabe

+1

'char.MaxValue' è 65535 – dtb

+0

Qual è il risultato del cronometro/cronometro di prova con la stringa di esempio? – Alex

Problemi correlati