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?
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?
Réponses:
Si cela semble laid, supprimez simplement l' ToCharArray
appel inutile .
Si vous souhaitez diviser par l'un \n
ou 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? ( StringSplitOptions
paramètre) - utilisez à la StringSplitOptions.None
place.
Environment.NewLine
c'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.
StringSplitOptions.RemoveEmptyEntries
.
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
[\r\n]{1,2}
\n\r
ou en \n\n
tant que saut de ligne unique, ce qui n'est pas correct.
Hello\n\nworld\n\n
un 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.
Vous pouvez utiliser Regex.Split:
string[] tokens = Regex.Split(input, @"\r?\n|\r");
Modifier: ajouté |\r
pour tenir compte des (anciens) terminateurs de ligne Mac.
\r
comme fin de ligne.
Si vous souhaitez conserver les lignes vides, supprimez simplement StringSplitOptions.
var result = input.Split(System.Environment.NewLine.ToCharArray());
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
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();
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();
}
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 \u000A
et les très anciens Mac utilisent un seul \u000D
caractère. La manière standard de traiter des mélanges arbitraires de ces caractères dans un seul fichier texte est la suivante:
\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 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 StringReader
instance à 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;
}