web-dev-qa-db-ja.com

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

複数行の文字列をどのように行に分割しますか?

私はこの方法を知っています

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

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

125
  • 見苦しい場合は、不要なToCharArray呼び出しを削除してください。

  • \nまたは\rのいずれかで分割する場合、2つのオプションがあります。

    • 配列リテラルを使用します-ただし、Windowsスタイルの行末\r\nには空の行が表示されます。

      var result = text.Split(new [] { '\r', '\n' });
      
    • Bartが示すように、正規表現を使用します。

      var result = Regex.Split(text, "\r\n|\r|\n");
      
  • 空の行を保持したい場合、C#にそれらを捨てるように明示的に指示するのはなぜですか? (StringSplitOptionsパラメーター)–代わりにStringSplitOptions.Noneを使用します。

155
Konrad Rudolph
using (StringReader sr = new StringReader(text)) {
    string line;
    while ((line = sr.ReadLine()) != null) {
        // do something
    }
}
116
Jack

更新:代替/非同期ソリューションについては here を参照してください。


これはうまく機能し、正規表現よりも高速です。

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

そして、これがExtension Method:

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
38
orad

Regex.Splitを使用できます。

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

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

36
Bart Kiers

空の行を保持する場合は、StringSplitOptionsを削除するだけです。

var result = input.Split(System.Environment.NewLine.ToCharArray());
9
Jonas Elfström

私はこれを持っていました 他の答え しかし、これは、ジャックの answer に基づいて、 かなり速い 少し遅いですが、非同期で動作するため、推奨される場合があります。

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

4
orad
      char[] archDelim = new char[] { '\r', '\n' };
      words = asset.text.Split(archDelim, StringSplitOptions.RemoveEmptyEntries); 
2
MAG TOR

少しねじれていますが、それを行うイテレータブロック:

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();
2
JDunkerley
    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
John Thompson

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

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

上記のルールは、 StringReader.ReadLine および関連する関数の動作を説明し、以下に示す関数は同じ結果を生成します。これは効率的なC#改行関数であり、これらのガイドラインを忠実に実装して、任意のシーケンスまたはCR/LFの組み合わせを正しく処理します。列挙された行には、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;
}
1
Glenn Slayden