2009-08-05 48 views
22

Tengo una cadena en la que tengo que hacer algunos reemplazos. Tengo un Dictionary<string, string> donde tengo pares de búsqueda-reemplazo definidos. He creado los siguientes métodos de extensión para realizar esta operación:C# Cadena reemplazar con diccionario

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; 
} 

¿Hay una forma mejor de hacerlo?

Respuesta

38

Si los datos se tokenized (es decir, "Estimado nombre $ $, a partir de $ $ Fecha de su saldo es $ cantidad $"), a continuación, un Regex pueden ser útiles:

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]); 
} 

Sin embargo, sin algo parecido esto, espero que su bucle Replace sea probablemente tanto como pueda, sin llegar a longitudes extremas. Si no está tokenizado, quizás lo perfile; ¿Es el Replace realmente un problema?

+0

Gran respuesta. Creo que su propuesta será mejor que repetir todo el diccionario, ya que la expresión regular solo reemplazará los tokens que se encontraron. No comprobará si puede haber alguien dentro.Entonces, si tengo un gran diccionario y un pequeño número de tokens en la cadena de entrada, eso realmente puede impulsar mi aplicación. – RaYell

+0

Muy útil. Refactoreé como un método de extensión para Regex, que no puedo mostrar en un comentario, así que lo agregaré como una respuesta extra algo redundante a continuación. –

+1

Esto lanzará una excepción si no se encuentra la clave. – Axel

9

Me parece razonable, excepto por una cosa: es sensible a los pedidos. Por ejemplo, tomemos una cadena de entrada de "$ x $ y" y un diccionario de reemplazo de:

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

Los resultados de la sustitución son ya sea "Foo Foo" o "$ y foo" dependiendo de la sustitución se realiza primero.

En su lugar, podría controlar el orden usando un List<KeyValuePair<string, string>>. La alternativa es caminar a través de la cadena asegurándose de no consumir los reemplazos en otras operaciones de reemplazo. Sin embargo, eso es mucho más difícil.

12

hacer esto con LINQ:

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

dict es su búsqueda, reemplazar pares definidos objeto Dictionary.

str es la cadena con la que debe reemplazar.

+0

+1 He usado expresiones regulares en el pasado, pero esto funcionó muy bien para mí. – clairestreb

4

Aquí hay una versión ligeramente re-factorizada de @ gran respuesta de Marc, para hacer que la funcionalidad disponible como un método de extensión de expresiones regulares:

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

cuando se utiliza solución de expresiones regulares de Marc Gravell, compruebe primero si una muestra está disponible usando es decir ContainsKey, esto para evitar errores KeyNotFoundException:

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

cuando se utiliza el código de ejemplo ligeramente modificada (primera parámetro tiene nombre diferente):

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

Esto produce lo siguiente:

"Estimado nombre $ $, a partir del 05 Ago 2009 Su balance es GBP200"

0

aquí están:

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

Puede agregar algunas explicaciones o comentarios a su respuesta, para que los lectores no tengan que inspeccionar de cerca el código para comprender lo que está proponiendo. Especialmente porque este es un fragmento bastante largo, mucho más largo que las otras soluciones propuestas. –

+0

Hace lo que el autor quería. A diferencia de otros, no usa expresiones regulares, y pasa a través de la línea solo una vez, lo cual es importante cuando necesita producir algunas decenas de reemplazos. – Albeoris

+0

Parece que ignora los límites de las palabras y parece ser muy propenso a errores. Por otro lado, no hay explicación presente, así que puedo estar equivocado. –

Cuestiones relacionadas