複数行の文字列を行に分割するにはどうすればよいですか?
私はこのように知っています
var result = input.Split("\n\r".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
少し醜く見え、空の行が失われます。より良い解決策はありますか?
複数行の文字列を行に分割するにはどうすればよいですか?
私はこのように知っています
var result = input.Split("\n\r".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
少し醜く見え、空の行が失われます。より良い解決策はありますか?
回答:
見苦しい場合は、不要なToCharArray
呼び出しを削除してください。
\n
またはのいずれか\r
で分割したい場合は、2つのオプションがあります。
配列リテラルを使用します。ただし、これはWindowsスタイルの行末に空の行を提供します\r\n
。
var result = text.Split(new [] { '\r', '\n' });
バートが示すように、正規表現を使用します。
var result = Regex.Split(text, "\r\n|\r|\n");
空の行を保持する必要がある場合、なぜC#にそれらを破棄するよう明示的に指示するのですか?(StringSplitOptions
パラメーター)– StringSplitOptions.None
代わりに使用します。
Environment.NewLine
は、私が考える限り、立ち入り禁止です。実際、すべてのソースプラットフォームを正しく処理できるので、可能なすべてのソリューションの中で、正規表現を使用するソリューションを好みます。
StringSplitOptions.RemoveEmptyEntries
。
using (StringReader sr = new StringReader(text)) {
string line;
while ((line = sr.ReadLine()) != null) {
// do something
}
}
string.Split
またはRegex.Split
)?
これはうまく機能し、Regexよりも高速です。
input.Split(new[] {"\r\n", "\r", "\n"}, StringSplitOptions.None)
"\r\n"
1つの改行として解釈されるように、配列の最初に配置することが重要です。上記の結果は、次のいずれかの正規表現ソリューションと同じ結果になります。
Regex.Split(input, "\r\n|\r|\n")
Regex.Split(input, "\r?\n|\r")
ただし、正規表現は約10倍遅いことがわかります。これが私のテストです:
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")
);
出力:
00:00:03.8527616
00:00:31.8017726
00:00:32.5557128
そしてここに拡張メソッドがあります:
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);
}
}
使用法:
input.GetLines() // keeps empty lines
input.GetLines(true) // removes empty lines
[\r\n]{1,2}
\n\r
か\n\n
、正しくない単一の改行として表示されます。
Hello\n\nworld\n\n
エッジケースはどうですか?それは明らかに、テキストを含む1行、その後に空行、テキストが続く別の行、空行が続きます。
あなたはRegex.Splitを使うことができます:
string[] tokens = Regex.Split(input, @"\r?\n|\r");
編集:(|\r
古い)Macラインターミネーターのアカウントに追加されました。
\r
。これらは行末としてのみ使用されるためです。
空の行を保持したい場合は、StringSplitOptionsを削除してください。
var result = input.Split(System.Environment.NewLine.ToCharArray());
私は、この持っていた他の答えをしかし、ジャックのに基づいて、この1、答えは、非常に高速であるわずかに遅いものの、それは非同期に動作しますので、好ましいかもしれません。
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;
}
}
}
}
使用法:
input.GetLines() // keeps empty lines
input.GetLines(true) // removes empty lines
テスト:
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()
);
出力:
00:00:03.9603894
00:00:00.0029996
00:00:04.8221971
少しツイストしましたが、それを行うイテレータブロック:
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);
}
次に呼び出すことができます:
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();
}
混合行末を適切に処理するのは難しいです。私たちが知っているように、回線終端文字は「改行」することができます(ASCII 10、 、\n
、\x0A
)、「\u000A
キャリッジリターン」(ASCII 13、 、\r
、\x0D
)、\u000D
またはそれらのいくつかの組み合わせ。DOSに戻ると、Windowsは2文字のシーケンスCR-LF \u000D\u000A
を使用しているため、この組み合わせでは1行しか出力されません。Unixは単一のを使用し、\u000A
非常に古いMacは単一の\u000D
文字を使用しました。これらの文字の任意の混合を単一のテキストファイル内で処理する標準的な方法は次のとおりです。
\u000D\u000A
)が続く場合、これら2つを一緒にに1行だけスキップします。String.Empty
行を返さない唯一の入力です(どの文字も少なくとも1行必要です)。上記のルールは、StringReader.ReadLineおよび関連する関数の動作を説明しており、以下に示す関数は同じ結果を生成します。任意のシーケンスまたはCR / LFの組み合わせを正しく処理するためにこれらのガイドラインを忠実に実装するのは、効率的なC#の改行機能です。列挙された行にはCR / LF文字が含まれていません。空の行は保持され、として返され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));
}
注:StringReader
呼び出しごとにインスタンスを作成するオーバーヘッドを気にしない場合は、代わりに次のC#7コードを使用できます。前述のように、上記の例の方が少し効率的かもしれませんが、これらの関数はどちらもまったく同じ結果を生成します。
public static IEnumerable<String> Lines(this String s)
{
using (var tr = new StringReader(s))
while (tr.ReadLine() is String L)
yield return L;
}