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?
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
.
using (StringReader sr = new StringReader(text)) {
string line;
while ((line = sr.ReadLine()) != null) {
// do something
}
}
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
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
Si vous souhaitez conserver des lignes vides, supprimez simplement StringSplitOptions.
var result = input.Split(System.Environment.NewLine.ToCharArray());
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
char[] archDelim = new char[] { '\r', '\n' };
words = asset.text.Split(archDelim, StringSplitOptions.RemoveEmptyEntries);
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();
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 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:
\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 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;
}