Meilleur moyen de diviser la chaîne en lignes


143

Comment diviser une chaîne multiligne en lignes?

Je sais de cette façon

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

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



1
J'aime cette solution, je ne sais pas comment la rendre plus facile. Le deuxième paramètre supprime bien sûr les vides.
NappingRabbit

Réponses:


172
  • Si cela semble laid, supprimez simplement l' ToCharArrayappel inutile .

  • Si vous souhaitez diviser par l'un \nou l' autre \r, vous avez deux options:

    • Utilisez un littéral de tableau - mais cela vous donnera des lignes vides pour les fins de ligne 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 dites-vous explicitement à C # de les jeter? ( StringSplitOptionsparamètre) - utilisez à la StringSplitOptions.Noneplace.


2
La suppression de ToCharArray rendra le code spécifique à la plate-forme (NewLine peut être '\ n')
Konstantin Spirin

1
@ Will: si vous faisiez référence à moi au lieu de Konstantin: je crois ( fortement ) que le code d'analyse devrait s'efforcer de fonctionner sur toutes les plates-formes (c'est-à-dire qu'il devrait également lire des fichiers texte qui ont été encodés sur des plates-formes différentes de la plate-forme d'exécution ). Donc, pour l'analyse, Environment.NewLinec'est interdit en ce qui me concerne. En fait, de toutes les solutions possibles, je préfère celle qui utilise des expressions régulières car seule elle gère correctement toutes les plates-formes sources.
Konrad Rudolph

2
@Hamish Eh bien, regardez simplement la documentation de l'énumération, ou regardez dans la question originale! C'est StringSplitOptions.RemoveEmptyEntries.
Konrad Rudolph

8
Que diriez-vous du texte qui contient '\ r \ n \ r \ n'. string.Split renverra 4 lignes vides, mais avec '\ r \ n' cela devrait en donner 2. Cela empire si '\ r \ n' et '\ r' sont mélangés dans un fichier.
nom d'utilisateur

1
@SurikovPavel Utilisez l'expression régulière. C'est certainement la variante préférée, car elle fonctionne correctement avec n'importe quelle combinaison de fins de ligne.
Konrad Rudolph

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

12
C'est l'approche la plus propre, à mon avis subjectif.
primo

5
Une idée en termes de performances (par rapport à string.Splitou Regex.Split)?
Uwe Keim

52

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


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 le "\r\n"premier dans le tableau afin 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")
);

Production:

00: 00: 03.8527616

00: 00: 31.8017726

00: 00: 32.5557128

et voici la méthode d'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);
    }
}

Usage:

input.GetLines()      // keeps empty lines

input.GetLines(true)  // removes empty lines

Veuillez ajouter quelques détails supplémentaires pour rendre votre réponse plus utile aux lecteurs.
Mohit Jain

Terminé. Ajout d'un test pour comparer ses performances avec la solution Regex.
orad

Modèle un peu plus rapide en raison de moins de retour en arrière avec la même fonctionnalité si l'on utilise[\r\n]{1,2}
ΩmegaMan

@OmegaMan Cela a un comportement différent. Il correspondra \n\rou en \n\ntant que saut de ligne unique, ce qui n'est pas correct.
orad

3
@OmegaMan Comment est Hello\n\nworld\n\nun cas de bord? Il s'agit clairement d'une ligne de texte, suivie d'une ligne vide, suivie d'une autre ligne de texte, suivie d'une ligne vide.
Brandin

36

Vous pouvez utiliser Regex.Split:

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

Modifier: ajouté |\rpour tenir compte des (anciens) terminateurs de ligne Mac.


Cela ne fonctionnera pas sur les fichiers texte de style OS X, car ils ne sont utilisés que \rcomme fin de ligne.
Konrad Rudolph

2
@Konrad Rudolph: AFAIK, '\ r' était utilisé sur de très vieux systèmes MacOS et n'est presque plus jamais rencontré. Mais si l'OP doit en tenir compte (ou si je me trompe), alors l'expression régulière peut facilement être étendue pour en tenir compte bien sûr: \ r? \ N | \ r
Bart Kiers

@Bart: Je ne pense pas que vous vous trompez , mais je l' ai à plusieurs reprises rencontré toutes les fins de ligne possibles dans ma carrière en tant que programmeur.
Konrad Rudolph

@Konrad, vous avez probablement raison. Mieux vaut prévenir que guérir, je suppose.
Bart Kiers

1
@ ΩmegaMan: Cela perdra des lignes vides, par exemple \ n \ n.
Mike Rosoft

9

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

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

2
NewLine peut être '\ n' et le texte d'entrée peut contenir "\ n \ r".
Konstantin Spirin

4

J'ai eu cette autre réponse, mais celle-ci, basée sur la réponse de Jack , est nettement plus rapide, car elle fonctionne de manière asynchrone, bien que légèrement plus lente.

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

Usage:

input.GetLines()      // keeps empty lines

input.GetLines(true)  // removes empty lines

Tester:

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

Production:

00: 00: 03.9603894

00: 00: 00.0029996

00: 00: 04.8221971


Je me demande si c'est parce que vous n'inspectez pas réellement les résultats de l'énumérateur, et par conséquent, il n'est pas exécuté. Malheureusement, je suis trop paresseux pour vérifier.
James Holwell

Oui, c'est vrai !! Lorsque vous ajoutez .ToList () aux deux appels, la solution StringReader est en fait plus lente! Sur ma machine, c'est 6,74s contre 5,10s
JCH2k

Ça a du sens. Je préfère toujours cette méthode car elle me permet d'obtenir des lignes de manière asynchrone.
orad

Vous devriez peut-être supprimer l'en-tête "meilleure solution" de votre autre réponse et modifier celle-ci ...
JCH2k

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

2

Légèrement tordu, mais un bloc itérateur 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();

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

Il est difficile de gérer correctement les fins de ligne mixtes . Comme nous le savons, les caractères de terminaison de ligne peuvent être « Saut de ligne » (ASCII 10, \n, \x0A, \u000A), « Retour chariot » (ASCII 13, \r, \x0D, \u000D), ou une combinaison d'entre eux. Pour revenir à DOS, Windows utilise la séquence de deux caractères CR-LF \u000D\u000A, donc cette combinaison ne doit émettre qu'une seule ligne. Unix utilise un seul \u000Aet les très anciens Mac utilisent un seul \u000Dcaractère. 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 SAUF ...
  • ... si un CR est immédiatement suivi de LF ( \u000D\u000A), alors ces deux sautent ensemble une seule ligne.
  • String.Empty est la seule entrée qui ne renvoie aucune ligne (tout caractère comporte au moins une ligne)
  • La dernière ligne doit être renvoyé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, et la fonction ci-dessous produit des résultats identiques. C'est une fonction de saut de ligne C # efficace qui implémente consciencieusement 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 et renvoyées comme 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));
}

Remarque: Si vous ne vous souciez pas de la surcharge de création d'une StringReaderinstance à chaque appel, vous pouvez utiliser le C # 7 suivant code suivant à la place. Comme indiqué, bien que l'exemple ci-dessus puisse être légèrement plus efficace, 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;
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.