Sostituisci più elementi stringa in C #


88

C'è un modo migliore per farlo ...

MyString.Trim().Replace("&", "and").Replace(",", "").Replace("  ", " ")
         .Replace(" ", "-").Replace("'", "").Replace("/", "").ToLower();

Ho esteso la classe delle corde per limitarla a un lavoro, ma esiste un modo più veloce?

public static class StringExtension
{
    public static string clean(this string s)
    {
        return s.Replace("&", "and").Replace(",", "").Replace("  ", " ")
                .Replace(" ", "-").Replace("'", "").Replace(".", "")
                .Replace("eacute;", "é").ToLower();
    }
}

Solo per divertimento (e per fermare gli argomenti nei commenti) ho spinto un punto su come confrontare i vari esempi di seguito.

https://gist.github.com/ChrisMcKee/5937656

L'opzione regex ha un punteggio terribile; l'opzione del dizionario è la più rapida; la versione a vento lungo della sostituzione del costruttore di stringhe è leggermente più veloce della mano corta.


1
Sulla base di ciò che hai nei tuoi benchmark, sembra che la versione del dizionario non stia facendo tutte le sostituzioni che sospetto sia ciò che la rende più veloce delle soluzioni StringBuilder.
rospo

1
@toad Ciao dal 2009; Ho aggiunto un commento qui sotto in aprile su questo errore lampante. L'essenza è aggiornata anche se ho saltato D. La versione del dizionario è ancora più veloce.
Chris McKee


1
@TotZam controlla almeno le date prima di contrassegnare le cose; questo è del 2009, cioè del 2012
Chris McKee,

Poiché molte risposte qui sembrano interessate alle prestazioni, credo che dovrebbe essere sottolineato che la risposta di Andrej Adamanko è probabilmente la più veloce per molte sostituzioni; certamente più veloce del concatenamento di .Replace () specialmente su una stringa di input di grandi dimensioni come indicato nella sua risposta.
person27

Risposte:


125

Più veloce - no. Più efficace, sì, se utilizzerai la StringBuilderclasse. Con la tua implementazione ogni operazione genera una copia di una stringa che in circostanze potrebbe compromettere le prestazioni. Le stringhe sono oggetti immutabili , quindi ogni operazione restituisce solo una copia modificata.

Se ti aspetti che questo metodo venga chiamato attivamente su multipli Stringsdi lunghezza significativa, potrebbe essere meglio "migrare" la sua implementazione nella StringBuilderclasse. Con esso qualsiasi modifica viene eseguita direttamente su quell'istanza, in modo da risparmiare operazioni di copia non necessarie.

public static class StringExtention
{
    public static string clean(this string s)
    {
        StringBuilder sb = new StringBuilder (s);

        sb.Replace("&", "and");
        sb.Replace(",", "");
        sb.Replace("  ", " ");
        sb.Replace(" ", "-");
        sb.Replace("'", "");
        sb.Replace(".", "");
        sb.Replace("eacute;", "é");

        return sb.ToString().ToLower();
    }
}

2
Per chiarezza, la risposta del dizionario è la più veloce stackoverflow.com/a/1321366/52912
Chris McKee

3
Nel tuo benchmark su gist.github.com/ChrisMcKee/5937656 il test del dizionario non è completo: non esegue tutte le sostituzioni e "" sostituisce "", non "". Non tutte le sostituzioni potrebbero essere il motivo per cui è più veloce nel benchmark. Anche la sostituzione dell'espressione regolare non è completa. Ma soprattutto la tua stringa TestData è molto breve. Come gli stati di risposta accettata, la stringa deve essere di lunghezza significativa affinché StringBuilder sia vantaggioso. Potresti ripetere il benchmark con stringhe da 10kB, 100kB e 1MB?
Leif

È un buon punto; così com'è è stato utilizzato per la pulizia dell'URL, quindi i test a 100kb - 1mb sarebbero stati irrealistici. Aggiornerò il benchmark in modo che utilizzi l'intera cosa, è stato un errore.
Chris McKee

Per ottenere le migliori prestazioni, ripeti i personaggi e sostituiscili tu stesso. Tuttavia, ciò può essere noioso se hai più di singole stringhe di caratteri (trovarle ti obbliga a confrontare più caratteri contemporaneamente, mentre la loro sostituzione richiede l'allocazione di più memoria e lo spostamento del resto della stringa).
Chayim Friedman

14

Se stai semplicemente cercando una bella soluzione e non hai bisogno di risparmiare qualche nanosecondo, che ne dici di un po 'di zucchero LINQ?

var input = "test1test2test3";
var replacements = new Dictionary<string, string> { { "1", "*" }, { "2", "_" }, { "3", "&" } };

var output = replacements.Aggregate(input, (current, replacement) => current.Replace(replacement.Key, replacement.Value));

Simile all'esempio C nel Gist (se guardi sopra l'istruzione linq più brutta è nel commento)
Chris McKee

1
Interessante che tu definisca una dichiarazione funzionale come "più brutta" di una procedurale.
TimS

non ho intenzione di discuterne; è solo una preferenza. Come dici tu, linq è semplicemente zucchero sintattico; e come ho detto avevo già messo l'equivalente sopra il codice :)
Chris McKee

14

questo sarà più efficiente:

public static class StringExtension
{
    public static string clean(this string s)
    {
        return new StringBuilder(s)
              .Replace("&", "and")
              .Replace(",", "")
              .Replace("  ", " ")
              .Replace(" ", "-")
              .Replace("'", "")
              .Replace(".", "")
              .Replace("eacute;", "é")
              .ToString()
              .ToLower();
    }
}

Davvero difficile da leggere. Sono sicuro che sai cosa fa, ma un Junior Dev si gratterà la testa per quello che succede realmente. Sono d'accordo - Cerco sempre anche la mano corta per scrivere qualcosa - Ma era solo per mia soddisfazione. Altre persone stavano andando fuori di testa al mucchio di disordine.
Piotr Kula

3
Questo è effettivamente più lento. BenchmarkOverhead ... 13 ms StringClean-user151323 ... 2843 ms StringClean-TheVillageIdiot ... 2921 ms Varia a seconda delle repliche ma la risposta vince gist.github.com/anonymous/5937596
Chris McKee

11

Forse un po 'più leggibile?

    public static class StringExtension {

        private static Dictionary<string, string> _replacements = new Dictionary<string, string>();

        static StringExtension() {
            _replacements["&"] = "and";
            _replacements[","] = "";
            _replacements["  "] = " ";
            // etc...
        }

        public static string clean(this string s) {
            foreach (string to_replace in _replacements.Keys) {
                s = s.Replace(to_replace, _replacements[to_replace]);
            }
            return s;
        }
    }

Aggiungi anche il suggerimento di New In Town su StringBuilder ...


5
Sarebbe più leggibile in questo modo:private static Dictionary<string, string> _replacements = new Dictionary<string, string>() { {"&", "and"}, {",", ""}, {" ", " "} /* etc */ };
ANeves pensa che SE sia il male

2
o ovviamente ... private static readonly Dictionary <string, string> Replacements = new Dictionary <string, string> () {{"&", "and"}, {",", ""}, {"", " " } /* eccetera */ }; public static string Clean (questa stringa s) {return Replacements.Keys.Aggregate (s, (current, toReplace) => current.Replace (toReplace, Replacements [toReplace])); }
Chris McKee

2
-1: L'uso di un dizionario non ha alcun senso qui. Usa solo un file List<Tuple<string,string>>. Questo cambia anche l'ordine delle sostituzioni e non è veloce come ad es s.Replace("a").Replace("b").Replace("c"). Non usare questo!
Thomas

6

C'è una cosa che può essere ottimizzata nelle soluzioni suggerite. Avere molte chiamate a Replace()fa sì che il codice esegua più passaggi sulla stessa stringa. Con stringhe molto lunghe le soluzioni potrebbero essere lente a causa della mancanza di capacità della cache della CPU. Potrebbe essere necessario considerare la sostituzione di più stringhe in un unico passaggio .


1
Molte risposte sembrano preoccupate per le prestazioni, nel qual caso questa è la migliore. Ed è semplice perché è solo un sovraccarico documentato di String.Replace in cui restituisci un valore atteso basato sulla corrispondenza, in questo esempio, usando un dizionario per abbinarli. Dovrebbe essere semplice da capire.
person27

4

Un'altra opzione che utilizza linq è

[TestMethod]
public void Test()
{
  var input = "it's worth a lot of money, if you can find a buyer.";
  var expected = "its worth a lot of money if you can find a buyer";
  var removeList = new string[] { ".", ",", "'" };
  var result = input;

  removeList.ToList().ForEach(o => result = result.Replace(o, string.Empty));

  Assert.AreEqual(expected, result);
}

Puoi dichiarare, var removeList = new List<string> { /*...*/ };quindi chiamare removeList.ForEach( /*...*/ );e semplificare il tuo codice. Nota anche che non risponde completamente alla domanda perché tutte le stringhe trovate vengono sostituite con String.Empty.
Tok

2

Sto facendo qualcosa di simile, ma nel mio caso sto facendo la serializzazione / deserializzazione, quindi devo essere in grado di andare in entrambe le direzioni. Trovo che l'uso di una stringa [] [] funzioni in modo quasi identico al dizionario, inclusa l'inizializzazione, ma puoi anche andare nella direzione opposta, riportando i sostituti ai loro valori originali, cosa per cui il dizionario non è davvero impostato.

Modifica: puoi utilizzare Dictionary<Key,List<Values>>per ottenere lo stesso risultato della stringa [] []


-1
string input = "it's worth a lot of money, if you can find a buyer.";
for (dynamic i = 0, repl = new string[,] { { "'", "''" }, { "money", "$" }, { "find", "locate" } }; i < repl.Length / 2; i++) {
    input = input.Replace(repl[i, 0], repl[i, 1]);
}

2
Dovresti considerare l'aggiunta di contesto alle tue risposte. Come una breve spiegazione di cosa sta facendo e, se pertinente, perché l'hai scritta nel modo in cui l'hai scritta.
Neil
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.