LINQ - Aufteilen einer Zeichenkette mit maximaler Länge, aber ohne Wörter auseinander zu schneiden

8

Ich habe eine einfache LINQ-Erweiterungsmethode ...

%Vor%

Dies nimmt einen String und es wird in eine Sammlung von Strings umgewandelt, die die angegebene Länge nicht überschreiten.

Das funktioniert gut - aber ich würde gerne weiter gehen. Es hackt Wörter in zwei Hälften. Ich brauche es nicht, um etwas Kompliziertes zu verstehen, ich möchte nur, dass es in der Lage ist, eine Zeichenfolge "früh" abzuschneiden, wenn es in der Mitte von Text geschnitten wird (im Grunde alles, was kein Leerzeichen ist) ).

Aber ich sauge an LINQ, also habe ich mich gefragt, ob irgendjemand eine Idee hatte, wie das geht. Ich weiß, was ich versuche, aber ich bin nicht sicher, wie ich es angehen soll.

Sagen wir also, ich habe den folgenden Text.

  

Dies ist ein Beispielblock von Text, den ich durch den String-Splitter passieren würde.

Ich rufe diese Methode length Ich würde folgendes bekommen.

  • Das ich
  • s ein sa
  • mple b
  • Sperre o
  • f Text
  • dass ich
  • würde
  • pass t
  • durch
  • das s
  • tring
  • geteilt
  • er.

Ich würde es lieber schlau genug machen, aufzuhören und eher wie ..

aussehen
  • Dies
  • ist ein
  • Beispiel
  

// schlechtes Beispiel, da das einzelne Wort die maximale Länge überschreitet, aber die Länge wäre in realen Szenarien, eher 200, größer.

Kann mir jemand helfen?

    
Ciel 29.12.2010, 17:12
quelle

8 Antworten

2

Ich werde das mit for loop lösen:

%Vor%

Zuerst dachte ich an einen Zip, aber es ist nicht gut hier.

und abweichende Ausführungsversion mit yield:

%Vor%     
Saeed Amiri 29.12.2010, 17:36
quelle
4

Update 2

Ich habe bemerkt, in Saeed's andere Antwort , dass er meinen Vorschlag übersprungen hat, da er eine Ausnahme ausgelöst hat. Das ist ein interessantes Thema. Wenn wir als Entwickler an einer Lösung eines Problems arbeiten, müssen wir die Ausnahmefälle berücksichtigen - die unerwarteten Eingaben, die ungültigen Zustände usw. Ich habe das Gefühl, weil die Anforderung der Frage war:

  

Ich will nur, dass es ein hacken kann   "früh" abschneiden, wenn es geschnitten wird   die Länge würde in die schneiden   Mitte des Textes (im Grunde alles   das ist kein Leerzeichen).

... dass, falls dies unmöglich ist (dh um einen Teilstring der angegebenen Länge zurückzugeben, ist es notwendig ein Wort einzufügen) halb, weil das Wort zu lang ist), wäre es angemessen, eine Ausnahme zu werfen. Aber das ist offensichtlich eine subjektive Angelegenheit. Jedoch zu erkennen, dass es mehr als einen Weg, um diese Katze zu Haut, ich habe meine Lösung (sowohl auf Pastebin und unten) aktualisiert, wenn ein zu übernehmen WordPolicy enum anstelle eines einfachen bool . In dieser Enum hat drei Werte: None (entspricht false von zuvor), ThrowIfTooLong (entspricht true von zuvor) und CutIfTooLong (wenn das Wort muss wird abgeschnitten schneide es einfach).

Ich habe auch Benchmarks für einige der anderen Antworten ** hinzugefügt, die unten aufgeführt sind. Beachten Sie, dass ich diesmal mehrere Läufe mit verschiedenen Parametern length (5, 10, 25, 200, 500, 1000) getestet habe. Für die kürzeste length (5) erscheinen die Ergebnisse mit dem Vorschlag von Saeed als eher gleichmäßig. Wenn length größer wird, wird die Leistung von Saeed's Vorschlag immer schlechter. Die Vorschläge von Simon und Jay scheinen bei großen Eingaben wesentlich besser skalierbar zu sein.

Denken Sie auch daran, dass das OP ausdrücklich gesagt hat, dass der Wert für length in einem realistischen Szenario "näher bei 200" wäre; Die Verwendung von 200 als Eingabe ist also nicht erfunden. Es ist in der Tat ein realistischer Fall.

Neue Benchmarks

%Vor%

** Leider konnte ich mit dem "großen" Input (der Kurzgeschichte) Jay's ursprünglichen Ansatz, der unglaublich teuer war, nicht testen - ein unglaublich tiefer Call-Stack aufgrund der Rekursion, plus eine wahnsinnige Anzahl sehr großer String-Zuweisungen aufgrund des string.Substring Aufrufs an einer riesigen Zeichenfolge .

Aktualisieren

Ich hoffe, dass dies nicht als defensiv wirkt, aber ich denke, dass einige sehr irreführende Informationen inmitten der Kommentare und einiger anderer Antworten hier präsentiert werden. Insbesondere Saeed's Antwort , die akzeptierte Antwort, hat einige deutliche Effizienzmängel. Das wäre keine so große Sache, außer dass Saeed in einigen Kommentaren behauptet hat, dass andere Antworten (einschließlich dieser) weniger effizient sind.

Leistungsvergleich

Erstens lügen Zahlen nicht. Hier ist ein Beispielprogramm I schrieb die Methode zu testen unten sowie Saeed Verfahren gegen B. Eingänge, lange und kurze.

Und hier sind einige Beispielergebnisse:

%Vor%

SplitOnLength für meine Methode unten, SaeedsOriginalApproach für die erste Vorschlag in Saeed Antwort, und SaeedsApproach für Saeed aktualisierte Antwort mit verzögerter Ausführung. Die Testeingaben waren:

  • Der gesamte Text von Franz Kafkas "In the Penal Colony" (ein kurzgeschichtlicher Beitrag)
  • Ein Auszug aus dem Text der OP-Frage (kurze Eingabe)
  • Ein length -Parameter von 25 (um einige längere Wörter aufzunehmen)

Beachten Sie, dass SplitOnLength in einer -Fraktion der in Saeed's Antwort vorgeschlagenen Methode ausgeführt wurde. Ich gebe jedoch zu, dass der Parameter length wahrscheinlich einen Einfluss darauf hat. Mit einem kleineren length wäre ich nicht überrascht, wenn sich die Leistung von SaeedSourceApproach und SaeedsApproach deutlich verbessert.

Erklärung

Nun, nur ein paar Anmerkungen zu dem, was ich hier denke. Auf Anhieb beginnt Saeed's Antwort mit einem Aufruf von string.Split . Jetzt ist hier etwas Interessantes, was Microsoft zu dieser Methode zu sagen hat :

  

Die Split-Methoden reservieren Speicher für   das zurückgegebene Array-Objekt und eine Zeichenfolge   Objekt für jedes Array-Element.Wenn dein   Anwendung erfordert optimal   Leistung oder Speicherverwaltung   Zuteilung ist kritisch in Ihrem   Anwendung, in Betracht ziehen, die   IndexOf oder IndexOfAny-Methode und   optional die Compare-Methode, um    Suchen Sie eine Teilzeichenfolge in einer Zeichenfolge . [Betonung meiner]

Mit anderen Worten: Die Methode string.Split wird von Microsoft nicht als geeignete Methode zur Erzielung einer optimalen Leistung empfohlen. Der Grund, warum ich die letzte Zeile hervorhebe, ist, dass Saeed die Effizienz von Antworten, die string.Substring verwenden, spezifisch in Frage stellt. Aber das ist der effizienteste Weg, um dieses Problem zu lösen , Punkt.

Dann in der von Saeed vorgeschlagenen Methode haben wir Code, der so aussieht:

%Vor%

* Dies ist der Grund, warum ich glaube, dass die Performance von Saeed's Ansatz mit höheren und höheren Längenparametern abnimmt. Als diejenigen von uns, die durch die Leistung der String-Verkettung vor wissen gebissen wurden, ist dies ein neues String-Objekt auf jedem Anhang zuweisen, was verschwenderisch ist. Das Problem wird nur noch schlimmer, wenn length länger wird. Beachten Sie, dass die Methode SplitOnLength unterhalb von nicht unter diesem Problem leidet .

Eine andere Möglichkeit, dies zu betrachten, ist einfach, die verschiedenen Schritte, die stattfinden müssen, zu durchbrechen. Belassen Sie N die Länge der Eingabezeichenfolge und K die angegebene maximale Länge einer Teilzeichenfolge.

Saeed's Antwort

  1. Zuerst string.Split(' ') . Dies erfordert das einmalige Aufzählen über die gesamte Zeichenkette und die Zuweisung eines Zeichenkettenobjekts für jedes Wort in der Zeichenkette - was wahrscheinlich mehr ist, als wir benötigen (wir werden fast sicher werden einige davon verketten) them) - sowie ein Array, das diese Objekte enthält. Lassen Sie die Anzahl der Strings im Array jetzt X sein.
  2. Dann gibt es eine zweite Enumeration über die X-Ergebnisse von # 1. Während dieser Enumeration gibt es 0-X-String-Verkettungen mit += , die zwischen 0-X neuen String-Objekten zugewiesen werden.

SplitOnLength (unten)

  1. Die Zeichenfolge ist wahrscheinlich nie vollständig aufgezählt. Es werden bei die meisten N-Vergleiche durchgeführt, aber in einem realistischeren Fall ist die Anzahl kleiner als N. Dies ist der Fall, weil der Algorithmus bei jeder Iteration optimistisch direkt zum nächsten "besten" Index geht und nur rückwärts schreitet wenn / wie nötig, um den neuesten Whitespace zu finden.
  2. Die einzigen neuen String-Objekte, die zugewiesen werden, sind genau diejenigen, die benötigt werden, mit string.Substring .

string.Substring ist übrigens sehr schnell. Es kann sein, weil es keine "extra" Arbeit zu tun gibt; die genaue Länge der Teilkette ist im Voraus bekannt, so dass während der Zuweisung kein Abfall erzeugt wird (wie es beispielsweise mit dem Operator += der Fall wäre).

Noch einmal, der Grund, warum ich dieses unheilvolle Update zu meiner Antwort hinzufüge, ist, auf die Leistungsprobleme mit Saeeds Vorschlag hinzuweisen und seine Behauptungen über die angeblichen Effizienzprobleme in einigen der anderen Antworten zu bestreiten.

Ursprüngliche Antwort

Mein instinktiver Ansatz bestand darin, mit dem, was Sie hatten, anzufangen und es leicht zu ergänzen, mit der Zugabe von ein wenig Code, der:

  1. Überprüfen Sie, ob auf index + length ein Leerzeichen folgt. und wenn nicht:
  2. Schritt rückwärts in die Zeichenfolge, bis ein Leerzeichen gefunden wird.

Hier ist die modifizierte SplitOnLength -Methode:

%Vor%

Dieser Ansatz hat den Vorteil, dass er nicht mehr als nötig arbeitet; Beachten Sie das Fehlen von string.Split call und die Tatsache, dass string.Substring nur an den Stellen aufgerufen wird, an denen das Ergebnis tatsächlich zurückgegeben wird. Mit dieser Methode werden keine überflüssigen String-Objekte erstellt, sondern nur die, die Sie wirklich wollen.

    
Dan Tao 29.12.2010 18:34
quelle
2

Verwenden Sie String.Split(' ') , um die einzelne Zeichenfolge in ein Array einzelner Wörter umzuwandeln. Führen Sie dann eine Iteration durch, indem Sie die längste Zeichenfolge erstellen, die (mit den neu hinzugefügten Leerzeichen) den Grenzwert unterschreitet, einen Zeilenumbruch anhängen und einen Zeilenumbruch ausführen.

    
KeithS 29.12.2010 17:29
quelle
1

Ich sauge auch mit LINQ :-), aber hier ist ein Code, der funktioniert, ohne etwas zuzuteilen (außer natürlich Ausgangswörter), löscht Leerzeichen, entfernt leere Zeichenfolgen, schneidet Zeichenfolgen und bricht Wörter nie (es ist eine Designauswahl) - Ich wäre an einem vollständigen LINQ-Äquivalent interessiert:

%Vor%     
Simon Mourier 29.12.2010 17:41
quelle
1

Ich musste eine Antwort geben, da ich der Meinung war, dass die anderen Antworten zu sehr auf Indexierung und komplizierte Logik basierten. Ich denke, meine Antwort ist ein bisschen einfacher.

%Vor%

Das Ergebnis für die im OP bereitgestellte Beispielzeichenfolge lautet:

%Vor%     
Enigmativity 30.12.2010 12:05
quelle
0
%Vor%

Testfall:

%Vor%

Ausgabe:

%Vor%     
Danny Chen 30.12.2010 05:32
quelle
0

Ok, ich testete alle Wege (jay RegEx Weg, nicht LINQ eins) auch ich bekam Ausnahme von Dan Taos Art und Weise, wenn ich ein maintainWords für alle Position auf true, so dass ich es überspringen.

Das habe ich getan:

%Vor%

Und:

%Vor%

Die Ergebnisse:

%Vor%

Testen Sie es einfach in Ihren PCs und bearbeiten Sie dies, um Ihr Testergebnis zu behalten oder die aktuelle Funktion zu verbessern. Ich bin mir sicher, wenn wir es mit größeren Strings testen, können wir einen großen Unterschied zwischen meinem und deinem Ansatz feststellen.

Bearbeiten: Ich habe meinen Ansatz für foreach und yield geändert, siehe oben meinen Code. Das Ergebnis ist:

%Vor%

Hier ist mein (Jay's) Test:

%Vor%

Ergebnisse:

%Vor%     
Saeed Amiri 30.12.2010 11:34
quelle
0

update:

Der Einzeiler:

%Vor%

Ich habe dies gegenüber der akzeptierten Antwort mit ~ 9,300 Zeichen Quelle (Lorem Ipsum x4) aufgeteilt nach oder vor 200 Zeichen.

10.000 Durchgänge:
 - Schleife dauert ~ 4.200 ms
 - meins dauert ~ 1.200 ms

ursprüngliche Antwort:

Diese Methode verkürzt das Ergebnis, um Wörter zu vermeiden, außer wenn das Wort die angegebene Länge überschreitet. In diesem Fall wird es unterbrochen.

%Vor%     
Jay 29.12.2010 17:47
quelle

Tags und Links