Wie kann ich die split ein string von strings und zählen der Trennzeichen verwenden .NET?
Gibt es viele ähnliche Fragen, aber anscheinend nicht perfekt übereinstimmen, das ist, warum ich Frage.
Ich möchte teilen Sie eine zufällige Zeichenfolge (z.B. 123xx456yy789
) von einer Liste von string-Begrenzungszeichen (z.B. xx
, yy
), und nehmen Sie das Trennzeichen in das Ergebnis (hier: 123
, xx
, 456
, yy
, 789
).
Gute Leistung ist ein netter bonus. Regex sollte vermieden werden, wenn möglich.
Update: ich habe einige performance-checks und die Ergebnisse verglichen (zu faul, um formell überprüfen, Sie wenn). Die getesteten Lösungen sind (in zufälliger Reihenfolge):
Andere Lösungen wurden nicht geprüft, weil entweder waren Sie ähnlich zu einer anderen Lösung oder Sie kamen zu spät.
Dies ist der test-code:
class Program
{
private static readonly List<Func<string, List<string>, List<string>>> Functions;
private static readonly List<string> Sources;
private static readonly List<List<string>> Delimiters;
static Program ()
{
Functions = new List<Func<string, List<string>, List<string>>> ();
Functions.Add ((s, l) => s.SplitIncludeDelimiters_Gabe (l).ToList ());
Functions.Add ((s, l) => s.SplitIncludeDelimiters_Guffa (l).ToList ());
Functions.Add ((s, l) => s.SplitIncludeDelimiters_Naive (l).ToList ());
Functions.Add ((s, l) => s.SplitIncludeDelimiters_Regex (l).ToList ());
Sources = new List<string> ();
Sources.Add ("");
Sources.Add (Guid.NewGuid ().ToString ());
string str = "";
for (int outer = 0; outer < 10; outer++) {
for (int i = 0; i < 10; i++) {
str += i + "**" + DateTime.UtcNow.Ticks;
}
str += "-";
}
Sources.Add (str);
Delimiters = new List<List<string>> ();
Delimiters.Add (new List<string> () { });
Delimiters.Add (new List<string> () { "-" });
Delimiters.Add (new List<string> () { "**" });
Delimiters.Add (new List<string> () { "-", "**" });
}
private class Result
{
public readonly int FuncID;
public readonly int SrcID;
public readonly int DelimID;
public readonly long Milliseconds;
public readonly List<string> Output;
public Result (int funcID, int srcID, int delimID, long milliseconds, List<string> output)
{
FuncID = funcID;
SrcID = srcID;
DelimID = delimID;
Milliseconds = milliseconds;
Output = output;
}
public void Print ()
{
Console.WriteLine ("S " + SrcID + "\tD " + DelimID + "\tF " + FuncID + "\t" + Milliseconds + "ms");
Console.WriteLine (Output.Count + "\t" + string.Join (" ", Output.Take (10).Select (x => x.Length < 15 ? x : x.Substring (0, 15) + "...").ToArray ()));
}
}
static void Main (string[] args)
{
var results = new List<Result> ();
for (int srcID = 0; srcID < 3; srcID++) {
for (int delimID = 0; delimID < 4; delimID++) {
for (int funcId = 3; funcId >= 0; funcId--) { //i tried various orders in my tests
Stopwatch sw = new Stopwatch ();
sw.Start ();
var func = Functions[funcId];
var src = Sources[srcID];
var del = Delimiters[delimID];
for (int i = 0; i < 10000; i++) {
func (src, del);
}
var list = func (src, del);
sw.Stop ();
var res = new Result (funcId, srcID, delimID, sw.ElapsedMilliseconds, list);
results.Add (res);
res.Print ();
}
}
}
}
}
Wie Sie sehen können, es war wirklich nur ein quick-and-dirty-test, aber ich lief der test mehrmals und mit verschiedenen um-und das Ergebnis war immer sehr konsequent. Die gemessenen Zeitspannen liegen im Bereich von Millisekunden bis zu Sekunden, für größere Datenmengen. Ich ignorierte die Werte im niedrigen Millisekundenbereich in meiner folgenden Bewertung, denn Sie Schienen in der Praxis vernachlässigbar. Hier ist die Ausgabe auf meine box:
S 0 D 0 F 3 11ms 1 S 0 D 0 F 2 7ms 1 S 0 D 0 F 1 6ms 1 S 0 D 0 F 0 4ms 0 S 0 D 1 F 3 28ms 1 S 0 D 1 F 2 8ms 1 S 0 D 1 F 1 7ms 1 S 0 D 1 F 0 3ms 0 S 0 D 2 F 3 30ms 1 S 0 D 2 F 2 8ms 1 S 0 D 2 F 1 6ms 1 S 0 D 2 F 0 3ms 0 S 0 D 3 F 3 30ms 1 S 0 D 3 F 2 10ms 1 S 0 D 3 F 1 8ms 1 S 0 D 3 F 0 3ms 0 S 1 D 0 F 3 9ms 1 9e5282ec-e2a2-4... S 1 D 0 F 2 6ms 1 9e5282ec-e2a2-4... S 1 D 0 F 1 5ms 1 9e5282ec-e2a2-4... S 1 D 0 F 0 5ms 1 9e5282ec-e2a2-4... S 1 D 1 F 3 63ms 9 9e5282ec - e2a2 - 4265 - 8276 - 6dbb50fdae37 S 1 D 1 F 2 37ms 9 9e5282ec - e2a2 - 4265 - 8276 - 6dbb50fdae37 S 1 D 1 F 1 29ms 9 9e5282ec - e2a2 - 4265 - 8276 - 6dbb50fdae37 S 1 D 1 F 0 22ms 9 9e5282ec - e2a2 - 4265 - 8276 - 6dbb50fdae37 S 1 D 2 F 3 30ms 1 9e5282ec-e2a2-4... S 1 D 2 F 2 10ms 1 9e5282ec-e2a2-4... S 1 D 2 F 1-10ms 1 9e5282ec-e2a2-4... S 1 D 2 F 0 12ms 1 9e5282ec-e2a2-4... S 1 D 3 F 3 73ms 9 9e5282ec - e2a2 - 4265 - 8276 - 6dbb50fdae37 S 1 D 3 F 2 40ms 9 9e5282ec - e2a2 - 4265 - 8276 - 6dbb50fdae37 S 1 D 3 F 1 33ms 9 9e5282ec - e2a2 - 4265 - 8276 - 6dbb50fdae37 S 1 D 3 F 0 30ms 9 9e5282ec - e2a2 - 4265 - 8276 - 6dbb50fdae37 S 2 D 0 F 3 10ms 1 0**634226552821... S 2 D 0 F 2 109ms 1 0**634226552821... S 2 D 0 F 1 5ms 1 0**634226552821... S 2 D 0 F 0 127ms 1 0**634226552821... S 2 D 1 F 3 184ms 21 0**634226552821... - 0**634226552821... - 0**634226552821... - 0**634226 552821... - 0**634226552821... - S 2 D 1 F 2 364ms 21 0**634226552821... - 0**634226552821... - 0**634226552821... - 0**634226 552821... - 0**634226552821... - S 2 D 1 F 1 134ms 21 0**634226552821... - 0**634226552821... - 0**634226552821... - 0**634226 552821... - 0**634226552821... - S 2 D 1 F 0 517ms 20 0**634226552821... - 0**634226552821... - 0**634226552821... - 0**634226 552821... - 0**634226552821... - S 2 D 2 F 3 688ms 201 0 ** 634226552821217... ** 634226552821217... ** 634226552821217... ** 6 34226552821217... ** S 2 D 2 F 2 2404ms 201 0 ** 634226552821217... ** 634226552821217... ** 634226552821217... ** 6 34226552821217... ** S 2 D 2 F 1 874ms 201 0 ** 634226552821217... ** 634226552821217... ** 634226552821217... ** 6 34226552821217... ** S 2 D 2 F 0 717ms 201 0 ** 634226552821217... ** 634226552821217... ** 634226552821217... ** 6 34226552821217... ** S 2 D 3 F 3 1205ms 221 0 ** 634226552821217... ** 634226552821217... ** 634226552821217... ** 6 34226552821217... ** S 2 D 3 F 2 3471ms 221 0 ** 634226552821217... ** 634226552821217... ** 634226552821217... ** 6 34226552821217... ** S 2 D 3 F 1 1008ms 221 0 ** 634226552821217... ** 634226552821217... ** 634226552821217... ** 6 34226552821217... ** S 2 D 3 F 0 1095ms 220 0 ** 634226552821217... ** 634226552821217... ** 634226552821217... ** 6 34226552821217... **
Ich die Ergebnisse verglichen und das ist, was ich gefunden habe:
- Alle 4 Funktionen sind schnell genug für die gemeinsame Nutzung.
- Die naive version (aka das, was ich schrieb zunächst) das Schlimmste in Bezug auf die Rechenzeit.
- Regex ist ein bisschen langsam, auf kleinen Datenmengen (wahrscheinlich aufgrund der Initialisierungs-overhead).
- Regex funktioniert gut auf großen Daten-und schlägt in eine ähnliche Geschwindigkeit wie die nicht-regex-Lösungen.
- Die leistungsmäßig am besten scheint zu sein, Guffa-version insgesamt, die zu erwarten ist, aus dem code.
- Gabe, die version manchmal lässt ein Element, aber das habe ich nicht untersuchen, diese (Fehler?).
Um das Thema abzuschließen, empfehle ich Regex, die ist halbwegs schnell. Wenn Leistung wichtig ist, würde ich es vorziehen, Guffa Umsetzung.
InformationsquelleAutor mafu | 2010-03-20
Du musst angemeldet sein, um einen Kommentar abzugeben.
Trotz Ihrer Abneigung gegen die Verwendung von regex es eigentlich schön bewahrt der Begrenzer durch die Verwendung einer Gruppe zusammen mit der
Regex.Split
Methode:Wenn Sie entfernen Sie die Klammern aus dem Muster, mit nur
"xx|yy"
die Trennzeichen sind nicht erhalten. Werden Sie sicher, dass Regex.Flucht auf das Muster, wenn Sie keine Metazeichen, die halten Besondere Bedeutung bei regex. Die Charaktere sind\, *, +, ?, |, {, [, (,), ^, $,., #
. Zum Beispiel, ein Trennzeichen.
werden sollte, entkam\.
. Gegeben eine Liste von Trennzeichen, müssen Sie "ODER" Ihnen durch das pipe -|
symbol und auch das ist ein Zeichen, das bekommt entgangen. Um richtig zu bauen, die Muster verwenden Sie den folgenden code (danke @Frank für den Hinweis):Den Klammern sind verkettet anstatt im Modell enthalten, da Sie wäre falsch entkommen für Ihre Zwecke.
EDIT: darüber hinaus, wenn die
delimiters
Liste geschieht leer zu sein, das endgültige Bild würde falsch sein()
und dies würde bewirken, dass leere entspricht. Um zu verhindern, dass dieser einen Scheck für die Trennzeichen können verwendet werden. Mit all diesen im Hinterkopf das snippet wird:Wenn Sie eine groß-und Kleinschreibung, um die Trennzeichen verwenden Sie die
RegexOptions.IgnoreCase
option:Regex.Split(input, pattern, RegexOptions.IgnoreCase)
EDIT #2: die Lösung, bisher entspricht, split-Token, das könnte ein substring von einer größeren Zeichenfolge. Wenn der split-token abgestimmt werden sollte vollständig, sondern als Teil eines Zeichens, wie beispielsweise ein Szenario, in dem Wörter in einem Satz verwendet, als Trennzeichen, dann die Wort-Grenze
\b
Metazeichen sollte Hinzugefügt werden, um die Muster.Betrachten Sie zum Beispiel diesen Satz (ja, es ist kitschig):
"Welcome to stackoverflow... where the stack never overflows!"
Ob die Klammern wurden
{ "stack", "flow" }
die derzeitige Lösung, würde split "stackoverflow" und zurück 3 Streicher{ "stack", "over", "flow" }
. Wenn Sie benötigt, um eine genaue übereinstimmung, dann ist der einzige Ort, diese teilen würde wäre das Wort "stack", der später in den Satz und nicht "stackoverflow".Zu erreichen, um eine genaue übereinstimmung zu Verhalten ändern, das Muster zu zählen
\b
wie in\b(delim1|delim2|delimN)\b
:Schließlich, wenn trimmen der Leerzeichen vor und nach dem Trennzeichen gewünscht, fügen Sie
\s*
um die Muster wie in\s*(delim1|delim2|delimN)\s*
. Dies kann kombiniert werden mit\b
wie folgt:Sie tun müssen, würde
pattern = "(" + String.Join("|", (from d in delimeters select Regex.Escape(d)).ToArray()) + ")"
weil eine der delimeters könnte.
oder|
oder was auch immer in Ihnen.+1 ich wusste nicht, dass Sie das machen könnte! Sehr schön. Sie müssen nur lösen Sie die Regex.Escape-code...
guter Punkt, habe ich verpasst. Wird nun Bearbeiten.
danke, und fertig 🙂
InformationsquelleAutor Ahmad Mageed
Ok, sorry, vielleicht dieses:
;
.er sich tatsächlich vorgestellt, eine praktikable Idee, obwohl. Vielleicht eine Liste der möglichen neuen delimitters, könnte eines oder mehrere Zeichen. Durchlaufen der Liste, überprüfen Sie, ob das möglich delimitter vorhanden ist, und verwenden Sie Nagg die Logik für die ersten delimitter, dass der test bestanden.
Stimmt, aber ich würde wirklich gerne eine Lösung, die ist nicht abhängig von der nicht-Existenz bestimmter Literale Trennzeichen in den string. Ich sehe nicht, wie das möglich ist, mit dieser Idee, nur mit einigen mapping, die wahrscheinlich verletzt, die Leistung zu schlecht. Ich bin offen für Gegenbeispiele, obwohl, natürlich.
Ich getrennt mit {".","?","!"} Aufteilen, jeden Satz in eine neue Zeile. Ich wollte das Trennzeichen am Ende. Diese Antwort war schnell und einfach. Modifiziert man Teil Quelle = Quelle.Ersetzen(Trennzeichen, Trennzeichen + ";"); Jetzt habe ich meine Punkte, Fragezeichen, etc, am Ende meiner Zeilen. Danke!
InformationsquelleAutor Nagg
Hier ist eine Lösung, die nicht mit einem regulären Ausdruck und nicht mehr als strings notwendig:
InformationsquelleAutor Gabe
Ich kam mit einer Lösung für etwas ähnliches eine Weile zurück. Effizient aufteilen einer Zeichenfolge können Sie eine Liste der nächsten Zeit jedes Trennzeichen. So minimieren Sie die Zeiten, die Sie suchen, für jedes Trennzeichen.
Dieser Algorithmus wird auch selbst durchführen, für eine lange Zeichenkette und eine große Anzahl von Trennzeichen:
(Mit Reservierung für eventuelle Fehler, ich warf gerade diese version jetzt zusammen und ich habe es noch nicht getestet vorher.)
InformationsquelleAutor Guffa
Eine naive Implementierung
InformationsquelleAutor mafu
Diese haben eine identische Semantik zu String.Split Standard-Modus (also nicht auch die leeren Token).
Es kann schneller gemacht werden, durch die Verwendung von unsicherem code zum Durchlaufen der Quell-Zeichenfolge, obwohl dies erfordert, dass Sie schreiben die iteration Mechanismus selbst vielmehr als die Verwendung von yield return.
Es ordnet das absolute minimum (ein substring nicht pro separator-token plus die Verpackung enumerator) so realistisch um die Leistung zu verbessern, müssten Sie zu:
Dem der code geschrieben ist, als eine Erweiterung Methode
Wie funktioniert Ihre Implementierung speichern Sie die Zuweisungen?
Ich nicht, erstellen Sie sub-strings für die separator-Token, eine kleine Verbesserung trivial, um Ihren hinzufügen (die ich sehe, haben Sie schon sone)
Ja, aber dein
foreach
Schleife weist einen neuen enumerator für das Trennzeichen-array für jedes Zeichen des input-string.foreach auf eine (compile-Zeit bekannt) array nicht zuweisen enumerator. Versuchen Sie es und sehen.
InformationsquelleAutor ShuggyCoUk
Meinen ersten Beitrag/Antwort...dies ist ein rekursiver Ansatz.
InformationsquelleAutor Damion