2009-08-05 8 views
22

J'ai une chaîne sur laquelle j'ai besoin de faire quelques remplacements. J'ai un Dictionary<string, string> où j'ai des paires de recherche-remplacement définies. J'ai créé les méthodes d'extension suivantes pour effectuer cette opération:Chaîne C# remplacer par dictionnaire

public static string Replace(this string str, Dictionary<string, string> dict) 
{ 
    StringBuilder sb = new StringBuilder(str); 

    return sb.Replace(dict).ToString(); 
} 

public static StringBuild Replace(this StringBuilder sb, 
    Dictionary<string, string> dict) 
{ 
    foreach (KeyValuePair<string, string> replacement in dict) 
    { 
     sb.Replace(replacement.Key, replacement.Value); 
    } 

    return sb; 
} 

Y a-t-il une meilleure façon de procéder?

Répondre

38

Si les données tokenizé (c.-à "Cher $ name $, à compter de la date $ $ le solde est $ montant $"), puis un Regex peut être utile:

static readonly Regex re = new Regex(@"\$(\w+)\$", RegexOptions.Compiled); 
static void Main() { 
    string input = @"Dear $name$, as of $date$ your balance is $amount$"; 

    var args = new Dictionary<string, string>(
     StringComparer.OrdinalIgnoreCase) { 
      {"name", "Mr Smith"}, 
      {"date", "05 Aug 2009"}, 
      {"amount", "GBP200"} 
     }; 
    string output = re.Replace(input, match => args[match.Groups[1].Value]); 
} 

Cependant, sans quelque chose comme Ceci, je pense que votre boucle Replace est probablement à peu près autant que vous pouvez faire, sans aller à des longueurs extrêmes. Si ce n'est pas tokenized, peut-être le profiler; est le Replace effectivement un problème?

+0

Grande réponse. Je pense que votre proposition sera meilleure que l'itération sur l'ensemble du dictionnaire car l'expression régulière ne remplacera que les jetons trouvés. Il ne vérifie pas pour tous ceux qui peuvent éventuellement être à l'intérieur.Donc, si j'ai un gros dictionnaire et un petit nombre de jetons dans la chaîne d'entrée qui peut réellement donner un coup de pouce à mon application. – RaYell

+0

Très utile. Je l'ai refactorisé comme une méthode d'extension pour Regex, que je ne peux pas montrer dans un commentaire, donc j'ajouterai comme une réponse supplémentaire quelque peu redondante ci-dessous. –

+1

Cela lèvera une exception si la clé n'est pas trouvée. – Axel

9

Cela me semble raisonnable, sauf pour une chose: c'est sensible à la commande. Par exemple, prenez une chaîne d'entrée de « $ x $ y » et un dictionnaire de remplacement:

"$x" => "$y" 
"$y" => "foo" 

Les résultats du remplacement sont soit « foo foo » ou « $ y foo » selon le remplacement est effectué en premier.

Vous pouvez contrôler la commande en utilisant un List<KeyValuePair<string, string>> à la place. L'alternative est de marcher à travers la chaîne en veillant à ne pas consommer les remplacements dans d'autres opérations de remplacement. Ce sera probablement beaucoup plus difficile.

12

le faire avec Linq:

var newstr = dict.Aggregate(str, (current, value) => 
    current.Replace(value.Key, value.Value)); 

dict est votre recherche-remplacer les paires définies objet Dictionary.

str est votre chaîne avec laquelle vous devez faire quelques remplacements.

+0

+1 J'ai utilisé regex par le passé, mais cela a très bien fonctionné pour moi. – clairestreb

4

Voici une version de @ grande réponse de Marc re-factorisé légèrement, pour rendre la fonctionnalité disponible en tant que méthode d'extension pour Regex:

static void Main() 
{ 
    string input = @"Dear $name$, as of $date$ your balance is $amount$"; 
    var args = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); 
    args.Add("name", "Mr Smith"); 
    args.Add("date", "05 Aug 2009"); 
    args.Add("amount", "GBP200"); 

    Regex re = new Regex(@"\$(\w+)\$", RegexOptions.Compiled); 
    string output = re.replaceTokens(input, args); 

    // spot the LinqPad user // output.Dump(); 
} 

public static class ReplaceTokensUsingDictionary 
{ 
    public static string replaceTokens(this Regex re, string input, IDictionary<string, string> args) 
    { 
     return re.Replace(input, match => args[match.Groups[1].Value]); 
    } 
} 
2

lors de l'utilisation de la solution RegEx de Marc Gravell, vérifiez d'abord si un jeton est disponible en utilisant par exemple ContainsKey, ceci pour éviter les erreurs de KeyNotFoundException:

string output = re.Replace(zpl, match => { return args.ContainsKey(match.Groups[1].Value) ? arg[match.Groups[1].Value] : match.Value; }); 

lorsque vous utilisez le code suivant exemple légèrement modifié (1er paramètre a un nom différent):

var args = new Dictionary<string, string>(
     StringComparer.OrdinalIgnoreCase) 
     { 
      {"nameWRONG", "Mr Smith"}, 
      {"date", "05 Aug 2009"}, 
      {"AMOUNT", "GBP200"} 
     }; 

ce produit les éléments suivants:

"Cher $ name $, en date du 5 août 2009 le solde est GBP200"

0

Vous êtes:

public static class StringExm 
{ 
    public static String ReplaceAll(this String str, KeyValuePair<String, String>[] map) 
    { 
     if (String.IsNullOrEmpty(str)) 
      return str; 

     StringBuilder result = new StringBuilder(str.Length); 
     StringBuilder word = new StringBuilder(str.Length); 
     Int32[] indices = new Int32[map.Length]; 

     for (Int32 characterIndex = 0; characterIndex < str.Length; characterIndex++) 
     { 
      Char c = str[characterIndex]; 
      word.Append(c); 

      for (var i = 0; i < map.Length; i++) 
      { 
       String old = map[i].Key; 
       if (word.Length - 1 != indices[i]) 
        continue; 

       if (old.Length == word.Length && old[word.Length - 1] == c) 
       { 
        indices[i] = -old.Length; 
        continue; 
       } 

       if (old.Length > word.Length && old[word.Length - 1] == c) 
       { 
        indices[i]++; 
        continue; 
       } 

       indices[i] = 0; 
      } 

      Int32 length = 0, index = -1; 
      Boolean exists = false; 
      for (int i = 0; i < indices.Length; i++) 
      { 
       if (indices[i] > 0) 
       { 
        exists = true; 
        break; 
       } 

       if (-indices[i] > length) 
       { 
        length = -indices[i]; 
        index = i; 
       } 
      } 

      if (exists) 
       continue; 

      if (index >= 0) 
      { 
       String value = map[index].Value; 
       word.Remove(0, length); 
       result.Append(value); 

       if (word.Length > 0) 
       { 
        characterIndex -= word.Length; 
        word.Length = 0; 
       } 
      } 

      result.Append(word); 
      word.Length = 0; 
      for (int i = 0; i < indices.Length; i++) 
       indices[i] = 0; 
     } 

     if (word.Length > 0) 
      result.Append(word); 

     return result.ToString(); 
    } 
} 
+1

Vous pourriez ajouter quelques explications ou commentaires à votre réponse, afin que les lecteurs n'aient pas à inspecter le code de près pour comprendre ce que vous proposez. Surtout parce que c'est un extrait assez long, beaucoup plus long que les autres solutions proposées. –

+0

Il fait ce que l'auteur voulait. Contrairement à un autre, il n'utilise pas d'expressions régulières, et ne traverse la ligne qu'une seule fois, ce qui est important lorsque vous devez produire quelques dizaines de remplacements. – Albeoris

+0

Cela ressemble à ignorer les limites des mots et semble être très sujet aux erreurs. D'un autre côté, aucune explication n'est présente, alors je peux me tromper. –