2009-08-05 4 views
22

Ho una stringa su cui ho bisogno di fare alcune sostituzioni. Ho un Dictionary<string, string> dove ho definito coppie di ricerca-sostituzione. Ho creato i seguenti metodi di estensione per eseguire questa operazione:C# String sostituire con dizionario

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

Esiste un modo migliore per farlo?

risposta

38

Se i dati sono in formato token (vale a dire "Caro $ nome $, a partire da $ data $ il saldo è $ importo $"), poi un Regex può essere 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]); 
} 

Tuttavia, senza qualcosa di simile questo, mi aspetto che il tuo ciclo Replace sia probabilmente il più possibile, senza andare a lunghezze estreme. Se non è tokenizzato, forse definiscilo; è il Replace in realtà un problema?

+0

Grande risposta. Penso che la tua proposta sarà effettivamente migliore di iterare su tutto il dizionario, poiché l'espressione regolare sostituirà solo i token che sono stati trovati. Non controllerà tutti quelli che possono essere all'interno.Quindi, se ho un grosso dizionario e un piccolo numero di token nella stringa di input, in realtà posso dare una spinta alla mia app. – RaYell

+0

Molto utile. L'ho refactored come metodo di estensione per Regex, che non posso mostrare in un commento, quindi aggiungerò come risposta extra un po 'ridondante qui sotto. –

+1

Questo genererà un'eccezione se la chiave non viene trovata. – Axel

9

Mi sembra ragionevole, tranne che per una cosa: è sensibile all'ordine. Ad esempio, prendere una stringa di input di "$ x $ y" e un dizionario sostituzione di:

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

I risultati della sostituzione siano sia "foo" o "$ y foo" a seconda di quale sostituzione viene eseguito per primo.

È possibile controllare l'ordine utilizzando invece List<KeyValuePair<string, string>>. L'alternativa è quella di camminare attraverso la stringa assicurandosi di non consumare le sostituzioni in ulteriori operazioni di sostituzione. È probabile che sia molto più difficile però.

12

farlo con LINQ:

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

dict è la ricerca-sostituzione coppie definite oggetto Dictionary.

str è la stringa con cui è necessario eseguire sostituzioni.

+0

+1 Ho usato regex in passato, ma per me ha funzionato alla grande. – clairestreb

4

Ecco una versione leggermente ri-presi di @ grande risposta di Marc, per rendere la funzionalità disponibile come un metodo di estensione per 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

quando si utilizza la soluzione RegEx di Marc Gravell, primo controllo se un token è disponibile utilizzando cioè ContainsKey, questo per evitare errori KeyNotFoundException:

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

quando si utilizza il seguente codice di esempio leggermente modificato (1 parametro ha nome):

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

Questo produce il seguente:

"Caro $ nome $, a partire dal 5 agosto 2009 il saldo è GBP200"

0

Ecco a voi:

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

Puoi aggiungere qualche spiegazione o commento alla tua risposta, in modo che i lettori non debbano ispezionare da vicino il codice per capire cosa stai proponendo. Soprattutto perché questo è un frammento lungo, molto più lungo rispetto alle altre soluzioni proposte. –

+0

Fa ciò che l'autore voleva. A differenza di un altro, non usa espressioni regolari e passa attraverso la linea solo una volta, il che è importante quando è necessario produrre alcune decine di sostituzioni. – Albeoris

+0

Sembra che ignori i limiti delle parole e sembra essere molto incline agli errori. D'altra parte, nessuna spiegazione è presente, quindi potrei sbagliarmi. –