web-dev-qa-db-fra.com

Le meilleur moyen de diviser une chaîne en lignes

Comment diviser une chaîne de plusieurs lignes en lignes?

Je sais de cette façon

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

semble un peu moche et perd des lignes vides. Y a-t-il une meilleure solution?

113
  • Si cela vous semble moche, supprimez simplement l'appel inutile ToCharArray.

  • Si vous souhaitez diviser par \n ou \r, vous avez deux options:

    • Utilisez un tableau littéral - mais cela vous donnera des lignes vides pour les fins de lignes de style Windows \r\n:

      var result = text.Split(new [] { '\r', '\n' });
      
    • Utilisez une expression régulière, comme indiqué par Bart:

      var result = Regex.Split(text, "\r\n|\r|\n");
      
  • Si vous souhaitez conserver les lignes vides, pourquoi demandez-vous explicitement à C # de les jeter? (paramètre StringSplitOptions) - utilisez plutôt StringSplitOptions.None.

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

Vous pouvez utiliser Regex.Split:

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

Edit: ajout de |\r pour prendre en compte les terminateurs de ligne Mac 

36
Bart Kiers

Mise à jour: voir ici pour une solution alternative/async.


Cela fonctionne très bien et est plus rapide que Regex:

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

Il est important d'avoir "\r\n" en premier dans le tableau pour qu'il soit pris comme un saut de ligne. Ce qui précède donne les mêmes résultats que l’une ou l’autre de ces solutions Regex:

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

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

Sauf que Regex s'avère être environ 10 fois plus lent. Voici mon test:

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

Sortie:

00: 00: 03.8527616

00: 00: 31.8017726

00: 00: 32.5557128

et voici la méthode Extension:

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

Utilisation:

input.GetLines()      // keeps empty lines

input.GetLines(true)  // removes empty lines
34
orad

Si vous souhaitez conserver des lignes vides, supprimez simplement StringSplitOptions.

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

J'avais cette autre réponse mais celle-ci, basée sur le réponse de Jack, est nettement plus rapide peut être préféré car il fonctionne de manière asynchrone, bien que légèrement plus lent.

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

Utilisation:

input.GetLines()      // keeps empty lines

input.GetLines(true)  // removes empty lines

Test:

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

Sortie:

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

Légèrement tordu, mais un itérateur le bloque pour le faire:

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

Vous pouvez alors appeler:

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

Il est difficile de gérer correctement les fins de ligne mixtes. Comme nous le savons, les caractères de fin de ligne peuvent être "saut de ligne" (ASCII 10, \n, \x0A, \u000A), "retour chariot" (ASCII 13, \r, \x0D, \u000D), ou une combinaison de ceux-ci. Pour revenir à DOS, Windows utilise la séquence CR-LF \u000D\u000A de deux caractères, de sorte que cette combinaison ne doit émettre qu'une seule ligne. Unix utilise un seul \u000A et les très vieux Mac utilisent un seul caractère \u000D. La manière standard de traiter des mélanges arbitraires de ces caractères dans un seul fichier texte est la suivante:

  • chaque caractère CR ou LF doit passer à la ligne suivante EXCEPT ...
  • ... si un CR est immédiatement suivi de LF (\u000D\u000A), alors ces deux ensemble sautent une seule ligne.
  • String.Empty est la seule entrée qui ne renvoie aucune ligne (tout caractère implique au moins une ligne)
  • La dernière ligne doit être retournée même si elle n'a ni CR ni LF.

La règle précédente décrit le comportement de StringReader.ReadLine et des fonctions associées. La fonction présentée ci-dessous produit des résultats identiques. C’est une fonction efficace de coupure de ligne C # qui implémente ces directives pour gérer correctement toute séquence ou combinaison arbitraire de CR/LF. Les lignes énumérées ne contiennent aucun caractère CR/LF. Les lignes vides sont conservées/retournées.

public static IEnumerable<String> Lines(this String s)
{
    int i, j = 0, c;
    char ch;
    if ((c = s.Length) > 0)
    {
    _more:
        i = j;
        do
            if ((ch = s[j]) == '\r' || ch == '\n')
                break;
        while (++j < c);

        yield return s.Substring(i, j - i);

        if (++j < c)
        {
            if (ch == '\r' && s[j] == '\n')      // CR-LF together skip only 1 line
                j++;
            if (j < c)
                goto _more;
        }
    }
}

Remarque: l'utilisation de goto permet à cette fonction de n'avoir qu'une seule instruction yield, ce qui simplifie considérablement le code IL généré par le compilateur de la machine d'état de l'itérateur. Si cela vous dérange et que la création d'une instance StringReader à chaque appel ne vous gêne pas, vous pouvez utiliser le code C # 7 suivant. Comme indiqué précédemment, ces deux fonctions produisent exactement les mêmes résultats.

public static IEnumerable<String> Lines(this String s)
{
    using (var tr = new StringReader(s))
        while (tr.ReadLine() is String L)
            yield return L;
}
0
Glenn Slayden