Dizeyi satırlara ayırmanın en iyi yolu


143

Çok satırlı dizeyi satırlara nasıl ayırırsınız?

Bu yolu biliyorum

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

biraz çirkin görünüyor ve boş satırları kaybediyor. Daha iyi bir çözüm var mı?



1
Bu çözümü seviyorum, nasıl kolaylaştıracağımı bilmiyorum. İkinci parametre elbette boşalmaları kaldırır.
NappingRabbit

Yanıtlar:


172
  • Çirkin görünüyorsa, gereksiz ToCharArrayaramayı kaldırın .

  • İkisinden birine bölmek istiyorsanız \nveya \riki seçeneğiniz vardır:

    • Bir dizi değişmezi kullanın - ancak bu size Windows stili satır sonları için boş satırlar verecektir \r\n:

      var result = text.Split(new [] { '\r', '\n' });
    • Bart tarafından belirtildiği gibi düzenli bir ifade kullanın:

      var result = Regex.Split(text, "\r\n|\r|\n");
  • Boş satırları korumak istiyorsanız, neden C # 'a onları atmasını söylüyorsunuz? ( StringSplitOptionsparametre) - StringSplitOptions.Nonebunun yerine kullanın.


2
ToCharArray kaldırıldığında kod platformuna özgü olur (NewLine '\ n' olabilir)
Konstantin Spirin

1
@Will: Konstantin yerine bana atıfta bulunma şansınız var: İnanıyorum ( kuvvetle ), ayrıştırma kodunun tüm platformlarda çalışmaya çalışmalı (yani , yürütme platformundan farklı platformlarda kodlanmış metin dosyalarını da okumalıdır) ). Ayrıştırma için, Environment.NewLineendişelendiğim kadarıyla hareketsizdir. Aslında, tüm olası çözümlerden düzenli ifadeleri kullanarak birini tercih ederim çünkü sadece tüm kaynak platformları doğru şekilde işler.
Konrad Rudolph

2
@Hamish Enumun belgelerine bakın veya orijinal soruya bakın! Öyle StringSplitOptions.RemoveEmptyEntries.
Konrad Rudolph

8
'\ R \ n \ r \ n' içeren metne ne dersiniz? string.Split 4 boş satır döndürür, ancak '\ r \ n' ile 2 vermelidir. '\ r \ n' ve '\ r' bir dosyada karıştırılırsa daha da kötüleşir.
kullanıcı adı

1
@SurikovPavel Normal ifadeyi kullanın. Kesinlikle tercih edilen varyant, çünkü hat uçlarının herhangi bir kombinasyonu ile doğru şekilde çalışıyor.
Konrad Rudolph

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

12
Benim görüşüme göre bu en temiz yaklaşım.
primo

5
Performans açısından herhangi bir fikir ( string.Splitveya ile karşılaştırıldığında Regex.Split)?
Uwe Keim

52

Güncelleme: Alternatif / zaman uyumsuz bir çözüm için buraya bakın .


Bu harika çalışıyor ve Regex'ten daha hızlı:

input.Split(new[] {"\r\n", "\r", "\n"}, StringSplitOptions.None)

İlk "\r\n"önce dizide yer almak önemlidir, böylece bir satır sonu olarak alınır. Yukarıdakiler bu Regex çözümlerinden herhangi biriyle aynı sonuçları vermektedir:

Regex.Split(input, "\r\n|\r|\n")

Regex.Split(input, "\r?\n|\r")

Bunun dışında Regex'in yaklaşık 10 kat daha yavaş olduğu ortaya çıktı. İşte benim testim:

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

Çıktı:

00: 00: 03,8527616

00: 00: 31,8017726

00: 00: 32,5557128

ve işte Uzatma Yöntemi:

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

Kullanımı:

input.GetLines()      // keeps empty lines

input.GetLines(true)  // removes empty lines

Cevabınızı okuyucular için daha kullanışlı hale getirmek için lütfen biraz daha ayrıntı ekleyin.
Mohit Jain

Bitti. Ayrıca performansını Regex çözümü ile karşılaştırmak için bir test ekledi.
orad

[\r\n]{1,2}
Kullanırsa

@OmegaMan Bunun bazı farklı davranışları var. Eşleşecek \n\rveya \n\ndoğru olmayan tek satır sonu olarak eşleşecek .
orad

3
@OmegaMan Hello\n\nworld\n\nKenar durumu nasıl ? Açıkça metin içeren bir satır, ardından boş bir satır, ardından metin içeren başka bir satır ve ardından boş bir satır gelir.
Brandin

36

Regex.Split kullanabilirsiniz:

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

Düzenleme: |\r(eski) Mac satır sonlandırıcıları hesaba eklendi .


Bu, yalnızca \rsatır sonu olarak kullanıldığından OS X stili metin dosyalarında çalışmaz .
Konrad Rudolph

2
@Konrad Rudolph: AFAIK, '\ r' çok eski MacOS sistemlerinde kullanıldı ve artık neredeyse hiç karşılaşılmıyor. Ancak OP'nin bunu hesaba katması gerekiyorsa (veya yanılıyorsam), regex elbette bunu hesaba katmak için kolayca genişletilebilir: \ r? \ N | \ r
Bart Kiers

@Bart: Ben sen yanlış sanmıyorum ama gelmiş defalarca programcı olarak kariyerimde tüm olası satır sonları karşılaştı.
Konrad Rudolph

@Konrad, muhtemelen haklısın. Üzgünümden daha güvenli, sanırım.
Bart Kiers

1
@ ΩmegaMan: Bu boş satırları kaybeder, örneğin \ n \ n.
Mike Rosoft

9

Boş satırları tutmak istiyorsanız StringSplitOptions öğesini kaldırın.

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

2
NewLine '\ n' olabilir ve giriş metni "\ n \ r" içerebilir.
Konstantin Spirin

4

Bunu vardı diğer cevabı Jack'in dayalı ama bu bir, cevap , önemli ölçüde daha hızlı olduğu zaman uyumsuz çalıştığı için olsa biraz daha yavaş, tercih edilebilir.

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;
            }
        }
    }
}

Kullanımı:

input.GetLines()      // keeps empty lines

input.GetLines(true)  // removes empty lines

Ölçek:

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

Çıktı:

00: 00: 03,9603894

00: 00: 00,0029996

00: 00: 04,8221971


Bunun aslında numaralandırıcının sonuçlarını denetlemediğinizden ve bu nedenle yürütülmediğinden mi merak ediyorum. Maalesef kontrol etmek için çok tembelim.
James Holwell

Evet, aslında !! Her iki çağrıya da .ToList () eklediğinizde, StringReader çözümü aslında daha yavaştır! Makinemde 6.74s vs 5.10s
JCH2k

Mantıklı. Bu yöntemi hala tercih ediyorum çünkü satırları eşzamansız olarak almamı sağlıyor.
orad

Belki diğer cevabınızdaki "daha iyi çözüm" başlığını kaldırmalı ve bunu düzenlemelisiniz ...
JCH2k

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

2

Biraz bükülmüş, ancak bunu yapmak için bir yineleyici blok:

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

Ardından şunları arayabilirsiniz:

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

Karışık hat uçlarını doğru şekilde kullanmak zor . Bildiğimiz gibi, hat sonlandırma karakterler "Satır Besleme" olabilir (ASCII 10, \n, \x0A, \u000A), "Satır Başı" (ASCII 13, \r, \x0D, \u000D) veya bunların bazıları kombinasyon. DOS'a geri dönersek, Windows iki karakterlik CR-LF dizisini kullanır \u000D\u000A, bu nedenle bu kombinasyon yalnızca tek bir satır yaymalıdır. Unix tek \u000A, çok eski Mac'ler ise tek bir \u000Dkarakter kullanır. Bu karakterlerin rastgele karışımlarını tek bir metin dosyasında ele almanın standart yolu aşağıdaki gibidir:

  • CR ve LF karakterlerinin her biri, DIŞINDA bir sonraki satıra atlamalıdır ...
  • ... bir CR'yi hemen LF ( \u000D\u000A) izliyorsa, bu ikisi birlikte yalnızca bir satırı atlar.
  • String.Empty hiçbir satır döndürmeyen tek girdidir (herhangi bir karakter en az bir satır içerir)
  • Son satır, CR veya LF içermese bile döndürülmelidir.

Yukarıdaki kural StringReader.ReadLine ve ilgili işlevlerin davranışını açıklar ve aşağıda gösterilen işlev aynı sonuçları verir. CR / LF'nin herhangi bir keyfi sekansını veya kombinasyonunu doğru bir şekilde işlemek için bu yönergeleri titizlikle uygulayan verimli bir C # satırı kesme işlevidir. Numaralandırılmış satırlar herhangi bir CR / LF karakteri içermiyor. Boş satırlar korunur ve olarak döndürülür 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));
}

Not: StringReaderHer çağrıda bir örnek oluşturmanın ek yükü yoksa, bunun yerine aşağıdaki C # 7 kodunu kullanabilirsiniz . Belirtildiği gibi, yukarıdaki örnek biraz daha verimli olsa da, bu işlevlerin her ikisi de aynı sonuçları verir.

public static IEnumerable<String> Lines(this String s)
{
    using (var tr = new StringReader(s))
        while (tr.ReadLine() is String L)
            yield return L;
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.