Ich möchte alle Sonderzeichen aus einer Zeichenfolge entfernen. Zulässige Zeichen sind AZ (Groß- oder Kleinbuchstaben), Zahlen (0-9), Unterstrich (_) oder das Punktzeichen (.).
Ich habe folgendes, es funktioniert, aber ich vermute (ich weiß!), Es ist nicht sehr effizient:
public static string RemoveSpecialCharacters(string str)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < str.Length; i++)
{
if ((str[i] >= '0' && str[i] <= '9')
|| (str[i] >= 'A' && str[i] <= 'z'
|| (str[i] == '.' || str[i] == '_')))
{
sb.Append(str[i]);
}
}
return sb.ToString();
}
Was ist der effizienteste Weg, dies zu tun? Wie würde ein regulärer Ausdruck aussehen und wie ist er mit einer normalen String-Manipulation zu vergleichen?
Die zu bereinigenden Zeichenfolgen sind ziemlich kurz und normalerweise zwischen 10 und 30 Zeichen lang.
Antworten:
Warum ist Ihre Methode Ihrer Meinung nach nicht effizient? Es ist tatsächlich eine der effizientesten Möglichkeiten, dies zu tun.
Sie sollten das Zeichen natürlich in eine lokale Variable einlesen oder einen Enumerator verwenden, um die Anzahl der Array-Zugriffe zu verringern:
Eine Sache, die eine solche Methode effizient macht, ist, dass sie gut skaliert. Die Ausführungszeit ist relativ zur Länge der Zeichenfolge. Es gibt keine bösen Überraschungen, wenn Sie es auf einer großen Saite verwenden würden.
Bearbeiten:
Ich habe einen schnellen Leistungstest durchgeführt und jede Funktion millionenfach mit einer 24-stelligen Zeichenfolge ausgeführt. Dies sind die Ergebnisse:
Ursprüngliche Funktion: 54,5 ms.
Meine vorgeschlagene Änderung: 47,1 ms.
Meins mit eingestellter StringBuilder-Kapazität: 43,3 ms.
Regulärer Ausdruck: 294,4 ms.
Edit 2: Ich habe im obigen Code die Unterscheidung zwischen AZ und az hinzugefügt. (Ich habe den Leistungstest erneut durchgeführt, und es gibt keinen erkennbaren Unterschied.)
Edit 3:
Ich habe die Lookup + Char [] -Lösung getestet und sie läuft in ca. 13 ms.
Der zu zahlende Preis ist natürlich die Initialisierung der riesigen Nachschlagetabelle und deren Speicherung. Nun, es sind nicht so viele Daten, aber es ist viel für eine so triviale Funktion ...
quelle
char[]
eher einen Puffer als einen verwendetStringBuilder
, hat laut meinen Tests einen leichten Vorteil gegenüber diesem. (Mine ist jedoch weniger lesbar, so dass sich der kleine Leistungsvorteil wahrscheinlich nicht lohnt.)char[]
Puffers (geringfügig) besser alsStringBuilder
bei der Skalierung auf Zeichenfolgen mit einer Länge von Zehntausenden von Zeichen.Nun, es sei denn, Sie müssen die Leistung wirklich aus Ihrer Funktion herausholen, wählen Sie einfach das, was am einfachsten zu warten und zu verstehen ist. Ein regulärer Ausdruck würde folgendermaßen aussehen:
Für zusätzliche Leistung können Sie es entweder vorkompilieren oder einfach beim ersten Aufruf kompilieren (nachfolgende Aufrufe sind schneller).
quelle
Ich schlage vor, eine einfache Nachschlagetabelle zu erstellen, die Sie im statischen Konstruktor initialisieren können, um eine beliebige Zeichenkombination auf gültig zu setzen. Auf diese Weise können Sie eine schnelle Einzelprüfung durchführen.
bearbeiten
Aus Gründen der Geschwindigkeit sollten Sie auch die Kapazität Ihres StringBuilder auf die Länge Ihrer Eingabezeichenfolge initialisieren. Dadurch werden Neuzuweisungen vermieden. Diese beiden Methoden zusammen bieten Ihnen Geschwindigkeit und Flexibilität.
eine andere Bearbeitung
Ich denke, der Compiler könnte es optimieren, aber aus Gründen des Stils und der Effizienz empfehle ich foreach anstelle von for.
quelle
for
undforeach
erzeugen Sie ähnlichen Code. Ich weiß allerdings nichts über Saiten. Ich bezweifle, dass die JIT die Array-ähnliche Natur von String kennt.quelle
foreach (char c in input.Where(c => char.IsLetterOrDigit(c) || allowedSpecialCharacters.Any(x => x == c))) buffer[idx++] = c;
Ein regulärer Ausdruck sieht folgendermaßen aus:
Wenn die Leistung jedoch sehr wichtig ist, empfehle ich Ihnen, einige Benchmarks durchzuführen, bevor Sie den "Regex-Pfad" auswählen ...
quelle
Wenn Sie eine dynamische Liste von Zeichen verwenden, bietet LINQ möglicherweise eine viel schnellere und elegantere Lösung:
Ich habe diesen Ansatz mit zwei der vorherigen "schnellen" Ansätze verglichen (Release-Kompilierung):
Beachten Sie, dass der Algorithmus leicht modifiziert ist - die Zeichen werden als Array übergeben und nicht fest codiert, was sich geringfügig auf die Dinge auswirken könnte (dh die anderen Lösungen hätten eine innere Schleife, um das Zeichenarray zu überprüfen).
Wenn ich mit einer LINQ where-Klausel zu einer fest codierten Lösung wechsle, sind die Ergebnisse:
Es könnte sich lohnen, sich LINQ oder einen modifizierten Ansatz anzusehen, wenn Sie eine allgemeinere Lösung schreiben möchten, anstatt die Liste der Zeichen fest zu codieren. LINQ bietet Ihnen definitiv präzisen, gut lesbaren Code - noch mehr als Regex.
quelle
Ich bin nicht davon überzeugt, dass Ihr Algorithmus alles andere als effizient ist. Es ist O (n) und betrachtet jedes Zeichen nur einmal. Besser geht es nicht, wenn Sie die Werte nicht auf magische Weise kennen, bevor Sie sie überprüfen.
Ich würde jedoch die Kapazität von Ihnen initialisieren
StringBuilder
auf die anfängliche Größe der Zeichenfolge . Ich vermute, dass Ihr wahrgenommenes Leistungsproblem auf die Neuzuweisung von Speicher zurückzuführen ist.Randnotiz: Überprüfen
A
-z
ist nicht sicher. Sie sind einschließlich[
,\
,]
,^
,_
, und `...Randnotiz 2: Um die Effizienz zu steigern, ordnen Sie die Vergleiche so an, dass die Anzahl der Vergleiche minimiert wird. (Im schlimmsten Fall sprechen Sie von 8 Vergleichen, denken Sie also nicht zu viel nach.) Dies ändert sich mit Ihrer erwarteten Eingabe, aber ein Beispiel könnte sein:
Randnotiz 3: Wenn Sie dies aus irgendeinem Grund WIRKLICH benötigen, um schnell zu sein, kann eine switch-Anweisung schneller sein. Der Compiler sollte eine Sprungtabelle für Sie erstellen, die nur einen einzigen Vergleich ergibt:
quelle
quelle
Sie können den regulären Ausdruck wie folgt verwenden:
quelle
Es scheint mir gut zu sein. Die einzige Verbesserung, die ich machen würde, ist die Initialisierung
StringBuilder
mit der Länge der Zeichenfolge.quelle
Ich stimme diesem Codebeispiel zu. Der einzige Unterschied ist, dass ich es in die Erweiterungsmethode vom Typ String mache. Damit Sie es in einer sehr einfachen Zeile oder einem Code verwenden können:
Vielen Dank an Guffa für Ihr Experiment.
quelle
Ich würde einen String-Ersatz durch einen regulären Ausdruck verwenden, der nach "Sonderzeichen" sucht und alle gefundenen Zeichen durch einen leeren String ersetzt.
quelle
Ich musste etwas Ähnliches für die Arbeit tun, aber in meinem Fall musste ich alles filtern, was kein Buchstabe, keine Zahl oder kein Leerzeichen ist (aber Sie können es leicht an Ihre Bedürfnisse anpassen). Die Filterung erfolgt clientseitig in JavaScript, aber aus Sicherheitsgründen mache ich die Filterung auch serverseitig. Da ich davon ausgehen kann, dass die meisten Zeichenfolgen sauber sind, möchte ich das Kopieren der Zeichenfolge vermeiden, es sei denn, ich muss es wirklich tun. Dies führte mich zur folgenden Implementierung, die sowohl für saubere als auch für schmutzige Zeichenfolgen eine bessere Leistung erbringen sollte.
quelle
Für S & Gs, Linq-ified Weg:
Ich denke jedoch nicht, dass dies der effizienteste Weg sein wird.
quelle
quelle
Verwenden:
Und du bekommst eine saubere Schnur
s
.erase()
entfernt alle Sonderzeichen und ist mit dermy_predicate()
Funktion hochgradig anpassbar .quelle
HashSet ist O (1)
Nicht sicher, ob es schneller als der vorhandene Vergleich ist
Ich habe getestet und dies nicht schneller als die akzeptierte Antwort.
Ich werde es so lassen, als ob Sie einen konfigurierbaren Satz von Zeichen benötigen würden. Dies wäre eine gute Lösung.
quelle
Ich frage mich, ob ein Regex-basierter Ersatz (möglicherweise kompiliert) schneller ist.
Müsste das testenjemand festgestellt hat, dass dies ~ 5 mal langsamer ist.Ansonsten sollten Sie den StringBuilder mit einer erwarteten Länge initialisieren, damit der Zwischenstring nicht kopiert werden muss, während er wächst.
Eine gute Zahl ist die Länge der ursprünglichen Zeichenfolge oder etwas etwas niedrigeres (abhängig von der Art der Funktionseingaben).
Schließlich können Sie eine Nachschlagetabelle (im Bereich 0..127) verwenden, um herauszufinden, ob ein Zeichen akzeptiert werden soll.
quelle
Der folgende Code hat die folgende Ausgabe (Schlussfolgerung ist, dass wir auch einige Speicherressourcen sparen können, die dem Array eine kleinere Größe zuweisen):
Sie können auch die folgenden Codezeilen hinzufügen, um das russische Gebietsschema zu unterstützen (die Arraygröße beträgt 1104):
quelle
Ich bin nicht sicher, ob es der effizienteste Weg ist, aber es funktioniert für mich
quelle
Hier werden viele Lösungen vorgeschlagen, von denen einige effizienter als andere sind, aber möglicherweise nicht sehr gut lesbar. Hier ist eine, die möglicherweise nicht die effizienteste ist, aber für die meisten Situationen durchaus verwendbar ist. Sie ist recht präzise und lesbar und nutzt Linq:
quelle
quelle
replaceAll
ist keine C # String-Funktion, sondern entweder Java oder JavaScriptquelle
Wenn Sie sich Gedanken über die Geschwindigkeit machen, verwenden Sie Zeiger, um die vorhandene Zeichenfolge zu bearbeiten. Sie könnten die Zeichenfolge anheften und einen Zeiger darauf erhalten, dann eine for-Schleife über jedes Zeichen ausführen und jedes ungültige Zeichen mit einem Ersatzzeichen überschreiben. Es wäre äußerst effizient und würde keine Zuweisung eines neuen Zeichenfolgenspeichers erfordern. Sie müssten Ihr Modul auch mit der Option "unsicher" kompilieren und den Modifikator "unsicher" zu Ihrem Methodenheader hinzufügen, um Zeiger zu verwenden.
quelle