2009-08-27 4 views

Répondre

21

plus rapide que dans moins de lignes-de-code:

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

plus rapide que dans la plus rapide performance serait probablement quelque chose comme ça (ne conserve pas l'ordre):

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" 

Test de performance

En cas de doute - le tester :)

 
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

Très agréable. Grande comparaison des performances aussi. La variation de performance est probablement encore plus visible avec de très grandes chaînes. – Alex

+1

J'ai répété le test de performance dans Release build avec un débogueur détaché (mais la même chaîne d'entrée). Je suis surpris par la performance de la réponse de Yuriy; c'est plutôt rapide! – dtb

+1

@dtb: Ce qui ralentit ma réponse par rapport à la vôtre, c'est que je préserve l'ordre d'origine dans la chaîne de sortie, ce qui nécessite une boucle supplémentaire dans la chaîne d'entrée. La technique que vous et moi utilisons pour trouver les dupes est exactement la même. – LukeH

0

Cet algorithme est général, peut être appliquée à toute langue

  1. créer une carte (HashTable) char-> int qui contient le nombre de chaque caractère trouvé, initialement vide
  2. scan the strin g une fois pour peupler la carte.
  3. créez une nouvelle chaîne vide qui conservera la sortie, vous devrez peut-être utiliser un StringBuilder.
  4. scan la chaîne (oula carte, selon la plus courte) copie uniquement des caractères qui une occurrence de 1 à la chaîne de sortie/StringBuilder
9

Voici un assez rapide maintien de l'ordre. Mais je serais un peu inquiet sur la façon dont LINQ fait groupe et où:

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: Celui-ci bat Luke dans certains cas encore plus lent que celui des DTB, mais il conserve l'ordre

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. Solution très propre. C'est incroyablement rapide aussi! – dtb

4

Cette on devrait être assez rapide (et il conserve l'ordre original):

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

Pourquoi pensez-vous que la création d'un StringBuilder probablement trop volumineux prendra moins de temps que de créer de l'espace à la volée? –

+0

@Yuri: J'ai benchmarké! J'ai testé avec des millions de chaînes aléatoires et le pré-dimensionnement du 'StringBuilder' était plus rapide dans la plupart des cas. Bien sûr, dans le monde réel, les cordes ne seraient probablement pas purement aléatoires. Dans cette situation, la différence de performance dépend du rapport entre les dupes et les non-dupes dans la chaîne source. – LukeH

+0

@Yuriy: Je viens de faire un benchmark sur une machine différente (Vista64 vs XP32) et les résultats étaient beaucoup plus proches. Sur la machine 64 bits, il semble ne faire aucune différence si le 'StringBuilder' est pré-alloué ou non. (Dans ce cas, il est probablement logique de ne pas déranger la pré-allocation et de nous sauver de la RAM.) – LukeH

2

Cela préserve l'ordre et, en fonction de mes tests, est 4 fois plus rapide que d'utiliser un HashSet. Cela suppose que votre plage de caractères est comprise entre 0 et 255 mais vous pouvez l'étendre facilement. Si vous envisagez d'utiliser ceci dans une boucle, déplacez le int[] c = new int[255]; et dans la fonction faites 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

aussi, je ne sais pas si le compilateur va dérouler ces boucles pour vous, mais vous pouvez essayer aussi http: // fr .wikipedia.org/wiki/Loop_unwinding – gabe

+1

'char.MaxValue' est 65535 – dtb

+0

Quel est le résultat de votre chronomètre/chronomètre de test avec l'exemple de chaîne? – Alex

Questions connexes