स्ट्रिंग में लाइनों को विभाजित करने का सबसे अच्छा तरीका


143

आप मल्टी-लाइन स्ट्रिंग को लाइनों में कैसे विभाजित करते हैं?

मैं इस तरह से जानता हूं

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

थोड़ा बदसूरत दिखता है और खाली लाइनें खो देता है। क्या कोई बेहतर समाधान है?



1
मुझे यह समाधान पसंद है, मुझे नहीं पता कि इसे आसान कैसे बनाया जाए। दूसरा पैरामीटर बेशक खाली कर देता है।
NappingRabbit

जवाबों:


172
  • यदि यह बदसूरत दिखता है, तो अनावश्यक ToCharArrayकॉल को हटा दें ।

  • यदि आप \nया तो विभाजित करना चाहते हैं या \r, आपके पास दो विकल्प हैं:

    • एक सरणी शाब्दिक का उपयोग करें - लेकिन इससे आपको विंडोज-स्टाइल लाइन अंत के लिए खाली लाइनें मिलेंगी \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एक नो-गो है जहाँ तक मेरा सवाल है। वास्तव में, सभी संभावित समाधानों में मैं नियमित अभिव्यक्ति का उपयोग करने वाले को पसंद करता हूं क्योंकि केवल सभी स्रोत प्लेटफार्मों को सही ढंग से संभालता है।
कोनराड रुडोल्फ

2
@ हमीश वैसे तो केवल एनम के दस्तावेज को देखें, या मूल प्रश्न को देखें! यह है StringSplitOptions.RemoveEmptyEntries
कोनराड रूडोल्फ

8
उस पाठ के बारे में कैसे जिसमें '\ r \ n \ r \ n' है। string.Split 4 खाली पंक्तियाँ लौटाएगा, हालाँकि '\ r \ n' के साथ यह 2. देना चाहिए। यह खराब हो जाता है यदि '\ r \ n' और 'r' एक फ़ाइल में मिश्रित होते हैं।
उपयोगकर्ता नाम

1
@SurikovPavel नियमित अभिव्यक्ति का उपयोग करें। यह निश्चित रूप से पसंदीदा संस्करण है, क्योंकि यह लाइन एंडिंग के किसी भी संयोजन के साथ सही ढंग से काम करता है।
कोनराड रुडोल्फ

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

12
यह सबसे स्वच्छ दृष्टिकोण है, मेरे व्यक्तिपरक राय में।
प्रिमो

5
प्रदर्शन के मामले में किसी भी विचार (की तुलना में string.Splitया Regex.Split)?
उवे कीम

52

अद्यतन: वैकल्पिक / async समाधान के लिए यहां देखें ।


यह महान काम करता है और रेगेक्स से तेज है:

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

"\r\n"सरणी में पहले होना महत्वपूर्ण है ताकि इसे एक पंक्ति विराम के रूप में लिया जाए। उपरोक्त इन रेगेक्स समाधानों के समान परिणाम देता है:

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: ०३.८५,२७,६१६

00: 00: ३१.८०,१७,७२६

00: 00: ३२.५५,५७,१२८

और यहाँ विस्तार विधि है:

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

पाठकों के लिए अपने उत्तर को अधिक उपयोगी बनाने के लिए कृपया कुछ और विवरण जोड़ें।
मोहित जैन

किया हुआ। रेगेक्स समाधान के साथ अपने प्रदर्शन की तुलना करने के लिए एक परीक्षण भी जोड़ा।
orad

एक ही कार्यक्षमता के साथ कम बैकट्रैकिंग के कारण थोड़ा तेज पैटर्न[\r\n]{1,2}
ManmegaMan

@OmegaMan कुछ अलग व्यवहार है। यह सिंगल लाइन-ब्रेक के रूप में \n\rया मैच करेगा \n\nजो सही नहीं है।
orad

3
@OmegaMan Hello\n\nworld\n\nएक किनारे का मामला कैसे है ? यह पाठ के साथ स्पष्ट रूप से एक पंक्ति है, जिसके बाद एक खाली रेखा है, पाठ के साथ एक और पंक्ति है, उसके बाद एक खाली पंक्ति है।
ब्रैंडिन

36

आप Regex.Split का उपयोग कर सकते हैं:

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

संपादित करें: |\rमैक लाइन टर्मिनेटर के लिए (पुराने) खाते में जोड़ा गया।


यह OS X स्टाइल टेक्स्ट फ़ाइलों पर काम नहीं करेगा, क्योंकि ये केवल \rलाइन एंडिंग के रूप में उपयोग होते हैं ।
कोनराड रुडोल्फ

2
@Konrad Rudolph: AFAIK, '\ r' का इस्तेमाल बहुत पुराने MacOS सिस्टम पर किया गया था और अब इसका सामना कभी नहीं किया गया है। लेकिन अगर ओपी को इसके लिए खाते की जरूरत है (या अगर मुझसे गलती हुई है), तो रेगेक्स को आसानी से इसे ध्यान में रखते हुए बढ़ाया जा सकता है: \ r? \ N | \ r
बार्ट किर्स

@Bart: मैं आप कर रहे हैं गलत नहीं लगता है, लेकिन मैं है बार-बार एक प्रोग्रामर के रूप में मेरे कैरियर में सभी संभव लाइन अंत का सामना करना पड़ा।
कोनराड रुडोल्फ

@ कोनराड, तुम शायद सही हो। खेद से बेहतर सुरक्षित, मुझे लगता है।
बार्ट कियर्स

1
@ ,MegaMan: कि खाली लाइनों खो देंगे, उदा \ n \ n।
माइक रोसॉफ्ट


4

मेरे पास इसका दूसरा जवाब था , लेकिन जैक के जवाब के आधार पर यह एक बहुत तेज है , क्योंकि इसे अतुल्यकालिक रूप से काम करता है, हालांकि यह थोड़ा धीमा हो सकता है।

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: ०३.९६,०३,८९४

00: 00: ००.००,२९,९९६

00: 00: ०४.८२,२१,९७१


मुझे आश्चर्य है कि अगर यह है क्योंकि आप वास्तव में प्रगणक के परिणामों का निरीक्षण नहीं कर रहे हैं, और इसलिए इसे निष्पादित नहीं किया जा रहा है। दुर्भाग्य से, मैं जाँच के लिए बहुत आलसी हूँ।
जेम्स हॉलवेल

हाँ, यह वास्तव में है !! जब आप दोनों कॉलों को .TLList () जोड़ते हैं, तो StringReader समाधान वास्तव में धीमा होता है! मेरी मशीन पर यह 6.74s बनाम 5.10s है
JCH2k

यह समझ आता है। मैं अभी भी इस पद्धति को पसंद करता हूं क्योंकि यह मुझे लाइनों को अतुल्यकालिक रूप से प्राप्त करने की अनुमति देता है।

हो सकता है कि आप अपने दूसरे जवाब पर "बेहतर समाधान" हेडर को हटा दें और इसे संपादित करें ...
JCH2k


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), या उनमें से कुछ संयोजन। डॉस पर वापस जाने पर, विंडोज दो-चरित्र अनुक्रम सीआर-एलएफ का उपयोग करता है \u000D\u000A, इसलिए इस संयोजन को केवल एक पंक्ति का उत्सर्जन करना चाहिए। यूनिक्स एक एकल का उपयोग करता है \u000A, और बहुत पुराने मैक एक एकल \u000Dचरित्र का उपयोग करते हैं । एकल पाठ फ़ाइल के भीतर इन पात्रों के मनमाने मिश्रण का इलाज करने का मानक तरीका निम्नानुसार है:

  • प्रत्येक और सीआर या LF चरित्र को अगली पंक्ति EXCEPT पर छोड़ देना चाहिए ...
  • ... अगर LF ( \u000D\u000A) द्वारा एक CR का तुरंत अनुसरण किया जाता है तो ये दोनों एक साथ होंगे सिर्फ एक लाइन छोड़ते हैं।
  • String.Empty केवल एक इनपुट है जो कोई रेखा नहीं लौटाता है (कोई भी वर्ण कम से कम एक पंक्ति में प्रवेश करता है)
  • अंतिम पंक्ति को लौटाया जाना चाहिए भले ही उसके पास न तो सीआर और न ही एलएफ हो।

पूर्ववर्ती नियम 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;
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.