文字列を行に分割する最良の方法


143

複数行の文字列を行に分割するにはどうすればよいですか?

私はこのように知っています

var result = input.Split("\n\r".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);

少し醜く見え、空の行が失われます。より良い解決策はありますか?



1
私はこのソリューションが好きですが、簡単にする方法がわかりません。2番目のパラメーターはもちろん空を削除します。
NappingRabbit 2018

回答:


172
  • 見苦しい場合は、不要な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代わりに使用します。


2
ToCharArrayを削除すると、コードがプラットフォーム固有になります(NewLineは '\ n'になる場合があります)
Konstantin Spirin

1
@Will:私は(信じて:あなたの代わりにコンスタンチンの私に言及していることを偶然オフに強くコードを解析すると、すべてのプラットフォーム上で動作するように努力すべきこと)(これも上にエンコードされたテキストファイルを読み込む必要があり、すなわち、異なる実行プラットフォームよりもプラットフォームを)。したがって、解析に関してEnvironment.NewLineは、私が考える限り、立ち入り禁止です。実際、すべてのソースプラットフォームを正しく処理できるので、可能なすべてのソリューションの中で、正規表現を使用するソリューションを好みます。
Konrad Rudolph

2
@ハミッシュまあ、列挙型のドキュメントを見るか、元の質問を見てください!ですStringSplitOptions.RemoveEmptyEntries
Konrad Rudolph

8
「\ r \ n \ r \ n」を含むテキストはどうですか。string.Splitは4行の空行を返しますが、 '\ r \ n'を指定すると2が返されます。1つのファイルに '\ r \ n'と '\ r'が混在している場合はさらに悪化します。
ユーザー名

1
@SurikovPavel正規表現を使用します。これは、行末の任意の組み合わせで正しく機能するため、間違いなく推奨されるバリアントです。
Konrad Rudolph

134
using (StringReader sr = new StringReader(text)) {
    string line;
    while ((line = sr.ReadLine()) != null) {
        // do something
    }
}

12
これは私の主観的な意見では、最もクリーンなアプローチです。
Primo 2013年

5
パフォーマンスの面で任意のアイデア(と比較string.SplitまたはRegex.Split)?
Uwe Keim

52

更新:代替/非同期ソリューションについては、こちらをご覧ください。


これはうまく機能し、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

読者のためにあなたの答えをより有用にするために、もう少し詳細を追加してください。
Mohit Jain 2014

できました。また、そのパフォーマンスを正規表現ソリューションと比較するテストを追加しました。
orad 2014

原因いずれかを使用する場合と同じ機能を持つより少ないバックトラッキングに幾分速いパターン[\r\n]{1,2}
ΩmegaMan

@OmegaManこれにはいくつかの異なる動作があります。一致する\n\r\n\n、正しくない単一の改行として表示されます。
orad 2015

3
@OmegaMan Hello\n\nworld\n\nエッジケースはどうですか?それは明らかに、テキストを含む1行、その後に空行、テキストが続く別の行、空行が続きます。
ブランディン

36

あなたはRegex.Splitを使うことができます:

string[] tokens = Regex.Split(input, @"\r?\n|\r");

編集:(|\r古い)Macラインターミネーターのアカウントに追加されました。


ただし、これはOS Xスタイルのテキストファイルでは機能しません\r。これらは行末としてのみ使用されるためです。
Konrad Rudolph、

2
@Konrad Rudolph:AFAIK、 '\ r'は非常に古いMacOSシステムで使用されており、もうほとんど発生しません。しかし、OPがそれを説明する必要がある場合(または私が誤っている場合)、正規表現はもちろんそれを説明するように簡単に拡張できます:\ r?\ n | \ r
Bart Kiers

@Bart:私はあなただと誤解しないと思うが、私はしている繰り返しプログラマーとしての私のキャリアの中で可能なすべての行末に遭遇しました。
Konrad Rudolph、

@Konrad、あなたはおそらく正しいです。申し訳ありませんが安全だと思います。
Bart Kiers、2009年

1
@ΩmegaMan:それは空行を失うでしょう、例えば\ n \ n。
Mike Rosoft

9

空の行を保持したい場合は、StringSplitOptionsを削除してください。

var result = input.Split(System.Environment.NewLine.ToCharArray());

2
NewLineは '\ n'にすることができ、入力テキストには「\ n \ r」を含めることができます。
Konstantin Spirin

4

私は、この持っていた他の答えをしかし、ジャックのに基づいて、この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


これは、実際には列挙子の結果を検査していないためか、実行されていないためかと思います。残念ながら、私はチェックするのが面倒です。
James Holwell、

はい、そうです!.ToList()を両方の呼び出しに追加すると、StringReaderソリューションは実際には遅くなります。私のマシンでは6.74秒と5.10秒です
JCH2k

それは理にかなっている。非同期に行を取得できるため、私はこの方法を好みます。
orad

多分あなたは他の答えの「より良い解決策」ヘッダーを削除してこれを編集するべきです...
JCH2k

4
string[] lines = input.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);

2

少しツイストしましたが、それを行うイテレータブロック:

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();

1
    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();
    }

1

混合行末を適切に処理するのは難しいです。私たちが知っているように、回線終端文字は「改行」することができます(ASCII 10、 、\n\x0A)、「\u000Aキャリッジリターン」(ASCII 13、 、\r\x0D)、\u000Dまたはそれらのいくつかの組み合わせ。DOSに戻ると、Windowsは2文字のシーケンスCR-LF \u000D\u000Aを使用しているため、この組み合わせでは1行しか出力されません。Unixは単一のを使用し、\u000A非常に古いMacは単一の\u000D文字を使用しました。これらの文字の任意の混合を単一のテキストファイル内で処理する標準的な方法は次のとおりです。

  • すべてのCRまたはLF文字は、次の行を除いてスキップする必要があります...
  • ... CRの直後にLF(\u000D\u000A)が続く場合、これら2つを一緒にに1行だけスキップします。
  • String.Empty 行を返さない唯一の入力です(どの文字も少なくとも1行必要です)。
  • CRもLFもない最後の行を返す必要があります。

上記のルールは、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;
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.