Wie teilt man mehrzeilige Zeichenfolgen in Zeilen auf?
Ich weiß es so
var result = input.Split("\n\r".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
sieht ein bisschen hässlich aus und verliert leere Zeilen. Gibt es eine bessere Lösung?
Wie teilt man mehrzeilige Zeichenfolgen in Zeilen auf?
Ich weiß es so
var result = input.Split("\n\r".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
sieht ein bisschen hässlich aus und verliert leere Zeilen. Gibt es eine bessere Lösung?
Antworten:
Wenn es hässlich aussieht, entfernen Sie einfach den unnötigen ToCharArray
Anruf.
Wenn Sie entweder \n
oder teilen möchten \r
, haben Sie zwei Möglichkeiten:
Verwenden Sie ein Array-Literal. Dadurch erhalten Sie jedoch leere Zeilen für Zeilenenden im Windows-Stil \r\n
:
var result = text.Split(new [] { '\r', '\n' });
Verwenden Sie einen regulären Ausdruck, wie von Bart angegeben:
var result = Regex.Split(text, "\r\n|\r|\n");
Wenn Sie leere Zeilen beibehalten möchten, warum weisen Sie C # ausdrücklich an, sie wegzuwerfen? ( StringSplitOptions
Parameter) - StringSplitOptions.None
stattdessen verwenden.
Environment.NewLine
ist es für mich ein No-Go. Tatsächlich bevorzuge ich von allen möglichen Lösungen die mit regulären Ausdrücken, da nur diese alle Quellplattformen korrekt handhabt.
StringSplitOptions.RemoveEmptyEntries
.
Dies funktioniert hervorragend und ist schneller als Regex:
input.Split(new[] {"\r\n", "\r", "\n"}, StringSplitOptions.None)
Es ist wichtig, "\r\n"
zuerst im Array zu haben, damit es als ein Zeilenumbruch genommen wird. Das Obige ergibt die gleichen Ergebnisse wie jede dieser Regex-Lösungen:
Regex.Split(input, "\r\n|\r|\n")
Regex.Split(input, "\r?\n|\r")
Nur dass Regex ungefähr zehnmal langsamer ist. Hier ist mein Test:
Action<Action> measure = (Action func) => {
var start = DateTime.Now;
for (int i = 0; i < 100000; i++) {
func();
}
var duration = DateTime.Now - start;
Console.WriteLine(duration);
};
var input = "";
for (int i = 0; i < 100; i++)
{
input += "1 \r2\r\n3\n4\n\r5 \r\n\r\n 6\r7\r 8\r\n";
}
measure(() =>
input.Split(new[] {"\r\n", "\r", "\n"}, StringSplitOptions.None)
);
measure(() =>
Regex.Split(input, "\r\n|\r|\n")
);
measure(() =>
Regex.Split(input, "\r?\n|\r")
);
Ausgabe:
00: 00: 03.8527616
00: 00: 31.8017726
00: 00: 32.5557128
und hier ist die Erweiterungsmethode:
public static class StringExtensionMethods
{
public static IEnumerable<string> GetLines(this string str, bool removeEmptyLines = false)
{
return str.Split(new[] { "\r\n", "\r", "\n" },
removeEmptyLines ? StringSplitOptions.RemoveEmptyEntries : StringSplitOptions.None);
}
}
Verwendung:
input.GetLines() // keeps empty lines
input.GetLines(true) // removes empty lines
[\r\n]{1,2}
\n\r
oder \n\n
als einzelner Zeilenumbruch, was nicht korrekt ist.
Hello\n\nworld\n\n
ein Edge Case? Es ist eindeutig eine Zeile mit Text, gefolgt von einer leeren Zeile, gefolgt von einer weiteren Zeile mit Text, gefolgt von einer leeren Zeile.
Sie könnten Regex.Split verwenden:
string[] tokens = Regex.Split(input, @"\r?\n|\r");
Bearbeiten: hinzugefügt |\r
, um (ältere) Mac-Leitungsabschlüsse zu berücksichtigen.
\r
als Zeilenende verwendet werden.
Wenn Sie leere Zeilen behalten möchten, entfernen Sie einfach die StringSplitOptions.
var result = input.Split(System.Environment.NewLine.ToCharArray());
Ich hatte diese andere Antwort, aber diese, basierend auf Jacks Antwort , ist deutlich schneller und wird möglicherweise bevorzugt, da sie asynchron arbeitet, obwohl sie etwas langsamer ist.
public static class StringExtensionMethods
{
public static IEnumerable<string> GetLines(this string str, bool removeEmptyLines = false)
{
using (var sr = new StringReader(str))
{
string line;
while ((line = sr.ReadLine()) != null)
{
if (removeEmptyLines && String.IsNullOrWhiteSpace(line))
{
continue;
}
yield return line;
}
}
}
}
Verwendung:
input.GetLines() // keeps empty lines
input.GetLines(true) // removes empty lines
Prüfung:
Action<Action> measure = (Action func) =>
{
var start = DateTime.Now;
for (int i = 0; i < 100000; i++)
{
func();
}
var duration = DateTime.Now - start;
Console.WriteLine(duration);
};
var input = "";
for (int i = 0; i < 100; i++)
{
input += "1 \r2\r\n3\n4\n\r5 \r\n\r\n 6\r7\r 8\r\n";
}
measure(() =>
input.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None)
);
measure(() =>
input.GetLines()
);
measure(() =>
input.GetLines().ToList()
);
Ausgabe:
00: 00: 03.9603894
00: 00: 00.0029996
00: 00: 04.8221971
Leicht verdreht, aber ein Iteratorblock dafür:
public static IEnumerable<string> Lines(this string Text)
{
int cIndex = 0;
int nIndex;
while ((nIndex = Text.IndexOf(Environment.NewLine, cIndex + 1)) != -1)
{
int sIndex = (cIndex == 0 ? 0 : cIndex + 1);
yield return Text.Substring(sIndex, nIndex - sIndex);
cIndex = nIndex;
}
yield return Text.Substring(cIndex + 1);
}
Sie können dann anrufen:
var result = input.Lines().ToArray();
private string[] GetLines(string text)
{
List<string> lines = new List<string>();
using (MemoryStream ms = new MemoryStream())
{
StreamWriter sw = new StreamWriter(ms);
sw.Write(text);
sw.Flush();
ms.Position = 0;
string line;
using (StreamReader sr = new StreamReader(ms))
{
while ((line = sr.ReadLine()) != null)
{
lines.Add(line);
}
}
sw.Close();
}
return lines.ToArray();
}
Es ist schwierig, gemischte Zeilenenden richtig zu handhaben . Wie wir wissen, können die Leitungsabschluss Zeichen "Line Feed" (ASCII 10, \n
, \x0A
, \u000A
), "Carriage Return" (ASCII 13 \r
, \x0D
, \u000D
) oder eine Kombination von ihnen. Zurück zu DOS verwendet Windows die zweistellige Sequenz CR-LF \u000D\u000A
, daher sollte diese Kombination nur eine einzige Zeile ausgeben. Unix verwendet ein einzelnes \u000A
und sehr alte Macs verwenden ein einzelnes \u000D
Zeichen. Die Standardmethode zum Behandeln beliebiger Mischungen dieser Zeichen in einer einzelnen Textdatei lautet wie folgt:
\u000D\u000A
) folgt, überspringen diese beiden zusammen nur eine Zeile.String.Empty
ist die einzige Eingabe, die keine Zeilen zurückgibt (jedes Zeichen enthält mindestens eine Zeile)Die vorstehende Regel beschreibt das Verhalten von StringReader.ReadLine und verwandten Funktionen. Die unten gezeigte Funktion führt zu identischen Ergebnissen. Es ist eine effiziente C # -Linienunterbrechungsfunktion, die diese Richtlinien pflichtbewusst umsetzt, um jede beliebige Sequenz oder Kombination von CR / LF korrekt zu handhaben. Die aufgezählten Zeilen enthalten keine CR / LF-Zeichen. Leere Zeilen bleiben erhalten und werden als zurückgegeben String.Empty
.
/// <summary>
/// Enumerates the text lines from the string.
/// ⁃ Mixed CR-LF scenarios are handled correctly
/// ⁃ String.Empty is returned for each empty line
/// ⁃ No returned string ever contains CR or LF
/// </summary>
public static IEnumerable<String> Lines(this String s)
{
int j = 0, c, i;
char ch;
if ((c = s.Length) > 0)
do
{
for (i = j; (ch = s[j]) != '\r' && ch != '\n' && ++j < c;)
;
yield return s.Substring(i, j - i);
}
while (++j < c && (ch != '\r' || s[j] != '\n' || ++j < c));
}
Hinweis: Wenn Ihnen der Aufwand beim Erstellen einer StringReader
Instanz bei jedem Aufruf nichts ausmacht , können Sie stattdessen den folgenden C # 7- Code verwenden. Wie bereits erwähnt, ist das obige Beispiel zwar etwas effizienter, beide Funktionen führen jedoch zu genau denselben Ergebnissen.
public static IEnumerable<String> Lines(this String s)
{
using (var tr = new StringReader(s))
while (tr.ReadLine() is String L)
yield return L;
}