J'aimerais séparer une chaîne d'une variable de longueur.
Il faut vérifier les limites pour ne pas exploser lorsque la dernière section de chaîne n’est pas aussi longue ou plus longue que la longueur Vous recherchez la version la plus succincte (mais compréhensible).
Exemple:
string x = "AAABBBCC";
string[] arr = x.SplitByLength(3);
// arr[0] -> "AAA";
// arr[1] -> "BBB";
// arr[2] -> "CC"
Vous devez utiliser une boucle:
public static IEnumerable<string> SplitByLength(this string str, int maxLength) {
for (int index = 0; index < str.Length; index += maxLength) {
yield return str.Substring(index, Math.Min(maxLength, str.Length - index));
}
}
Alternative:
public static IEnumerable<string> SplitByLength(this string str, int maxLength) {
int index = 0;
while(true) {
if (index + maxLength >= str.Length) {
yield return str.Substring(index);
yield break;
}
yield return str.Substring(index, maxLength);
index += maxLength;
}
}
2dakota du Nord alternative: (pour ceux qui ne supportent pas while(true)
)
public static IEnumerable<string> SplitByLength(this string str, int maxLength) {
int index = 0;
while(index + maxLength < str.Length) {
yield return str.Substring(index, maxLength);
index += maxLength;
}
yield return str.Substring(index);
}
Version facile à comprendre:
string x = "AAABBBCC";
List<string> a = new List<string>();
for (int i = 0; i < x.Length; i += 3)
{
if((i + 3) < x.Length)
a.Add(x.Substring(i, 3));
else
a.Add(x.Substring(i));
}
Bien que de préférence le 3 devrait être un gentil const.
Ce n'est pas particulièrement succinct, mais je pourrais utiliser une méthode d'extension comme celle-ci:
public static IEnumerable<string> SplitByLength(this string s, int length)
{
for (int i = 0; i < s.Length; i += length)
{
if (i + length <= s.Length)
{
yield return s.Substring(i, length);
}
else
{
yield return s.Substring(i);
}
}
}
Notez que je retourne un IEnumerable<string>
, pas un tableau. Si vous voulez convertir le résultat en tableau, utilisez ToArray
:
string[] arr = x.SplitByLength(3).ToArray();
Voici ce que je ferais:
public static IEnumerable<string> EnumerateByLength(this string text, int length) {
int index = 0;
while (index < text.Length) {
int charCount = Math.Min(length, text.Length - index);
yield return text.Substring(index, charCount);
index += length;
}
}
Cette méthode fournirait une exécution différée (ce qui n’a pas d’importance sur une classe immuable telle que string
, mais il convient de le noter).
Ensuite, si vous vouliez qu'une méthode remplisse un tableau pour vous, vous pourriez avoir:
public static string[] SplitByLength(this string text, int length) {
return text.EnumerateByLength(length).ToArray();
}
La raison pour laquelle je choisirais le nom EnumerateByLength
plutôt que SplitByLength
pour la méthode "core" est que string.Split
renvoie un string[]
, donc dans mon esprit, il existe une priorité pour les méthodes dont le nom commence par Split
retourner des tableaux.
C'est juste moi, cependant.
Ma solution:
public static string[] SplitToChunks(this string source, int maxLength)
{
return source
.Where((x, i) => i % maxLength == 0)
.Select(
(x, i) => new string(source
.Skip(i * maxLength)
.Take(maxLength)
.ToArray()))
.ToArray();
}
En fait, je préfère utiliser List<string>
au lieu de string[]
.
Utilisation de Batch
from MoreLinq
, sur .Net 4.0:
public static IEnumerable<string> SplitByLength(this string str, int length)
{
return str.Batch(length, String.Concat);
}
Sur 3.5 Concat a besoin d'un tableau, nous pouvons donc utiliser Concat
avec ToArray
ou, new String
:
public static IEnumerable<string> SplitByLength(this string str, int length)
{
return str.Batch(length, chars => new String(chars.ToArray()));
}
Il peut sembler peu intuitif de considérer une chaîne comme une collection de caractères. Par conséquent, une manipulation de chaîne peut être proposée.
UPD: Utiliser du Linq pour le rendre succinct
static IEnumerable EnumerateByLength(string str, int len)
{
Match m = (new Regex(string.Format("^(.{{1,{0}}})*$", len))).Match(str);
if (m.Groups.Count <= 1)
return Empty;
return (from Capture c in m.Groups[1].Captures select c.Value);
}
Version initiale:
static string[] Empty = new string [] {};
static string[] SplitByLength(string str, int len)
{
Regex r = new Regex(string.Format("^(.{{1,{0}}})*$",len));
Match m = r.Match(str);
if(m.Groups.Count <= 1)
return Empty;
string [] result = new string[m.Groups[1].Captures.Count];
int ix = 0;
foreach(Capture c in m.Groups[1].Captures)
{
result[ix++] = c.Value;
}
return result;
}
Encore une légère variante (classique mais simple et pragmatique):
class Program
{
static void Main(string[] args) {
string msg = "AAABBBCC";
string[] test = msg.SplitByLength(3);
}
}
public static class SplitStringByLength
{
public static string[] SplitByLength(this string inputString, int segmentSize) {
List<string> segments = new List<string>();
int wholeSegmentCount = inputString.Length / segmentSize;
int i;
for (i = 0; i < wholeSegmentCount; i++) {
segments.Add(inputString.Substring(i * segmentSize, segmentSize));
}
if (inputString.Length % segmentSize != 0) {
segments.Add(inputString.Substring(i * segmentSize, inputString.Length - i * segmentSize));
}
return segments.ToArray();
}
}
private string[] SplitByLength(string s, int d)
{
List<string> stringList = new List<string>();
if (s.Length <= d) stringList.Add(s);
else
{
int x = 0;
for (; (x + d) < s.Length; x += d)
{
stringList.Add(s.Substring(x, d));
}
stringList.Add(s.Substring(x));
}
return stringList.ToArray();
}
private void button2_Click(object sender, EventArgs e)
{
string s = "AAABBBCCC";
string[] a = SplitByLenght(s,3);
}
private string[] SplitByLenght(string s, int split)
{
//Like using List because I can just add to it
List<string> list = new List<string>();
// Integer Division
int TimesThroughTheLoop = s.Length/split;
for (int i = 0; i < TimesThroughTheLoop; i++)
{
list.Add(s.Substring(i * split, split));
}
// Pickup the end of the string
if (TimesThroughTheLoop * split != s.Length)
{
list.Add(s.Substring(TimesThroughTheLoop * split));
}
return list.ToArray();
}
J'ai eu le scénario étrange où j'avais segmenté une chaîne, puis réarrangé les segments (c'est-à-dire inversé) avant de les concaténer, et j'ai ensuite eu besoin d'inverser la segmentation. Voici une mise à jour de la réponse acceptée par @SLaks :
/// <summary>
/// Split the given string into equally-sized segments (possibly with a 'remainder' if uneven division). Optionally return the 'remainder' first.
/// </summary>
/// <param name="str">source string</param>
/// <param name="maxLength">size of each segment (except the remainder, which will be less)</param>
/// <param name="remainderFirst">if dividing <paramref name="str"/> into segments would result in a chunk smaller than <paramref name="maxLength"/> left at the end, instead take it from the beginning</param>
/// <returns>list of segments within <paramref name="str"/></returns>
/// <remarks>Original method at https://stackoverflow.com/questions/3008718/split-string-into-smaller-strings-by-length-variable </remarks>
private static IEnumerable<string> ToSegments(string str, int maxLength, bool remainderFirst = false) {
// note: `maxLength == 0` would not only not make sense, but would result in an infinite loop
if(maxLength < 1) throw new ArgumentOutOfRangeException("maxLength", maxLength, "Should be greater than 0");
// correct for the infinite loop caused by a nonsensical request of `remainderFirst == true` and no remainder (`maxLength==1` or even division)
if( remainderFirst && str.Length % maxLength == 0 ) remainderFirst = false;
var index = 0;
// note that we want to stop BEFORE we reach the end
// because if it's exact we'll end up with an
// empty segment
while (index + maxLength < str.Length)
{
// do we want the 'final chunk' first or at the end?
if( remainderFirst && index == 0 ) {
// figure out remainder size
var remainder = str.Length % maxLength;
yield return str.Substring(index, remainder);
index += remainder;
}
// normal stepthrough
else {
yield return str.Substring(index, maxLength);
index += maxLength;
}
}
yield return str.Substring(index);
}//--- fn ToSegments
(J'ai également corrigé un bogue dans la version originale de while
, ce qui donnait un segment vide si maxLength==1
)