2009-08-05 9 views
22

Mam ciąg znaków, na którym muszę wykonać niektóre zamienniki. Mam Dictionary<string, string>, gdzie mam zdefiniowane pary wyszukiwania-zamiany. Stworzyłem następujące metody rozszerzenia do wykonania tej operacji:C# String zamień na słownik

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

Czy jest lepszy sposób to zrobić?

Odpowiedz

38

Jeśli dane są tokenized (czyli "Drogi $ $ name, poczynając od $ Date $ saldo wynosi $ kwota $"), a następnie Regex mogą być przydatne:

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

Jednak bez czegoś takiego to, spodziewam się, że twoja pętla Replace jest prawdopodobnie w przybliżeniu tak duża, jak tylko możesz, bez sięgania do ekstremalnych długości. Jeśli nie jest tokenizowany, być może profiluj go; czy Replace jest rzeczywiście problemem?

+0

Wielkiej odpowiedź. Myślę, że twoja propozycja będzie w rzeczywistości lepsza niż iteracja w całym słowniku, ponieważ regex zastąpi jedynie tokeny, które zostały znalezione. Nie sprawdzi każdego, kto może być w środku.Więc jeśli mam duży słownik i niewielką liczbę tokenów w ciągu wejściowym, które faktycznie mogą zwiększyć moją aplikację. – RaYell

+0

Bardzo przydatne. Przefiltrowałem to jako metodę rozszerzenia dla Regex, której nie mogę pokazać w komentarzu, więc dodam jako nieco zbędną dodatkową odpowiedź poniżej. –

+1

Spowoduje to zgłoszenie wyjątku, jeśli klucz nie zostanie znaleziony. – Axel

9

Wydaje mi się rozsądny, z wyjątkiem jednego: jest wrażliwy na zamówienia. Na przykład, wziąć ciąg wejściowy „$ x $ y” i słownik odtworzeniowej:

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

Wyniki wymiana są albo „bla bla” lub „$ y foo” w zależności od których wymiana jest wykonywane jako pierwsze.

Możesz kontrolować zamawianie, używając zamiast tego List<KeyValuePair<string, string>>. Alternatywą jest przejście przez ciąg, upewniając się, że nie zużywasz zamienników w kolejnych operacjach wymiany. To prawdopodobnie będzie dużo trudniejsze.

12

zrobić z Linq:

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

DICT jest wyszukiwanie zamień par zdefiniowane słownik obiekt.

str to ciąg znaków, który należy zastąpić.

+0

+1 Użyłem regex w przeszłości, ale to działało dla mnie świetnie. – clairestreb

4

Oto lekko ponownie uwzględnione wersja @ wielką odpowiedź Marca, aby funkcjonalność dostępna jako metoda rozszerzenie do 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

przy użyciu roztworu REGEX Marc Gravell, w pierwszej kolejności należy sprawdzić, czy token jest dostępny używając tj containsKey, to aby uniknąć błędów KeyNotFoundException:

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

przy użyciu następującego nieznacznie zmodyfikowany kod przykładowy (1st parametr ma inna nazwa):

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

ten produkuje następujące:

"Drogi $ $ name, a od 05 sierpnia 2009 roku saldo jest GBP200"

0

Tutaj jesteś:

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

Możesz dodać wyjaśnienie lub komentarz do swojej odpowiedzi, aby czytelnicy nie musieli dokładnie sprawdzać kodu, aby zrozumieć, co proponujesz. Zwłaszcza, że ​​jest to dość długi fragment, o wiele dłuższy niż inne proponowane rozwiązania. –

+0

Robi to, co chciał autor. W przeciwieństwie do innych, nie używa wyrażeń regularnych i przechodzi przez linię tylko raz, co jest ważne, gdy trzeba wyprodukować kilka dziesiątek zamienników. – Albeoris

+0

Wygląda na to, że ignoruje granice słów i wydaje się być podatny na błędy. Z drugiej strony, nie ma żadnego wyjaśnienia, więc mogę się mylić. –