Teilen eines Strings in Stücke einer bestimmten Größe


218

Angenommen, ich hätte eine Zeichenfolge:

string str = "1111222233334444"; 

Wie kann ich diese Saite in Stücke von einiger Größe zerlegen?

Wenn Sie dies beispielsweise in Größen von 4 aufteilen, werden Zeichenfolgen zurückgegeben:

"1111"
"2222"
"3333"
"4444"

18
Warum LINQ oder reguläre Ausdrücke verwenden, wenn die Standardfunktionen zur Zeichenfolgenmanipulation von C # dies mit weniger Aufwand und mehr Geschwindigkeit tun können? Was passiert auch, wenn die Zeichenfolge eine ungerade Anzahl von Zeichen hat?
Ian Kemp

7
"Ich möchte Schleifen vermeiden" - warum?
Mitch Wheat

12
Die Verwendung einer einfachen Schleife bietet definitiv die beste Leistung.
Guffa

4
nichesoftware.co.nz/blog/200909/linq-vs-loop-performance ist ein ziemlich guter Vergleich zwischen linq und der tatsächlichen Schleife über ein Array. Ich bezweifle, dass Sie linq jemals schneller finden werden als manuell geschriebenen Code, da es weiterhin Laufzeitdelegierte aufruft, die schwer zu optimieren sind. Linq macht aber mehr Spaß :)
Blindy

2
Unabhängig davon, ob Sie LINQ oder reguläre Ausdrücke verwenden, ist die Schleife immer noch vorhanden.
Anton Tykhyy

Antworten:


247
static IEnumerable<string> Split(string str, int chunkSize)
{
    return Enumerable.Range(0, str.Length / chunkSize)
        .Select(i => str.Substring(i * chunkSize, chunkSize));
}

Bitte beachten Sie, dass möglicherweise zusätzlicher Code erforderlich ist, um Kantenfälle ordnungsgemäß zu behandeln ( nulloder leere Eingabezeichenfolgen, chunkSize == 0nicht durch teilbare Länge der Eingabezeichenfolgen chunkSizeusw.). Die ursprüngliche Frage enthält keine Anforderungen für diese Randfälle, und im wirklichen Leben können die Anforderungen variieren, sodass sie nicht in den Geltungsbereich dieser Antwort fallen.


3
@ Harry Guter Fang! Dies kann durch einen ternären Drop-In-Ausdruck für den Parameter count der Teilzeichenfolge behoben werden. So etwas wie : (i * chunkSize + chunkSize <= str.Length) ? chunkSize : str.Length - i * chunkSize. Ein weiteres Problem ist, dass diese Funktion nicht berücksichtigt, dass str null ist. Dies kann behoben werden, indem die gesamte return-Anweisung in einen anderen ternären Ausdruck eingeschlossen wird : (str != null) ? ... : Enumerable.Empty<String>();.
Drew Spickes

7
Dies war in der Nähe, aber im Gegensatz zu dem vorherigen 30 upvoters hatte ich aus der Schleife Zählgrenze des Bereichs zu ändern , str.Length / chunkSizeumdouble length = str.Length; double size = chunkSize; int count = (int)Math.Ceiling(length/size); return Enumerable.Range(0, count)...
Spalt

4
@KonstantinSpirin Ich stimme zu, wenn der Code funktioniert hat. Es wird nur der Fall behandelt, in dem eine Zeichenfolge ein Vielfaches von chunkSize ist und der Rest der Zeichenfolge verloren geht. Bitte ändern. Denken Sie auch daran, dass LINQ und seine Magie für jemanden, der nur nach einer Lösung für dieses Problem suchen möchte, nicht so einfach zu verstehen sind. Eine Person muss nun verstehen, was die Funktionen Enumerable.Range () und .Select () tun. Ich werde nicht argumentieren, dass Sie dies verstehen sollten, um C # /. NET-Code zu schreiben, da diese Funktionen seit vielen Jahren in der BCL vorhanden sind.
CodeMonkeyKing

6
Themenstarter sagte in Kommentaren, dass StringLength % 4 will always be 0. Wenn Linqes nicht so einfach zu verstehen ist, gibt es andere Antworten, die Schleifen und Ausbeuten verwenden. Jeder kann die Lösung wählen, die ihm am besten gefällt. Sie können Ihren Code als Antwort posten und die Leute werden gerne dafür stimmen.
Konstantin Spirin

3
Enumerable.Range (0, (str.Length + chunkSize - 1) / chunkSize) .Select (i => str.Substring (i * chunkSize, Math.Min (str.Length - i * chunkSize, chunkSize))
Sten Petrov

135

In einer Kombination aus Tauben + Konstatins Antworten ...

static IEnumerable<string> WholeChunks(string str, int chunkSize) {
    for (int i = 0; i < str.Length; i += chunkSize) 
        yield return str.Substring(i, chunkSize);
}

Dies funktioniert für alle Zeichenfolgen, die in eine ganze Anzahl von Blöcken aufgeteilt werden können, und löst ansonsten eine Ausnahme aus.

Wenn Sie Zeichenfolgen beliebiger Länge unterstützen möchten, können Sie den folgenden Code verwenden:

static IEnumerable<string> ChunksUpto(string str, int maxChunkSize) {
    for (int i = 0; i < str.Length; i += maxChunkSize) 
        yield return str.Substring(i, Math.Min(maxChunkSize, str.Length-i));
}

Das OP erklärte jedoch ausdrücklich, dass er dies nicht benötige; es ist etwas länger und schwerer zu lesen, etwas langsamer. Im Sinne von KISS und YAGNI würde ich mich für die erste Option entscheiden: Es ist wahrscheinlich die effizienteste Implementierung, die möglich ist, und es ist sehr kurz, lesbar und löst vor allem eine Ausnahme für fehlerhafte Eingaben aus.


4
+1 ein Nicken wert. irgendwie trifft Nagel auf Kopf. er sucht nach prägnantem sytnax und du gibst auch die (wahrscheinlich) bessere leistung.
Taube

7
Und wenn Sie es "statisch ... Chunk (dieser String str, int chunkSize) {" machen, haben Sie sogar noch ein "neues" C # -Feature drin. Dann können Sie "1111222233334444" schreiben .Chunk (4).
MartinStettner

1
@ MartinStettner: Das ist sicherlich eine anständige Idee, wenn dies eine übliche Operation ist.
Eamon Nerbonne

Sie sollten nur den letzteren Code einfügen. Ersteres setzt voraus, dass Sie verstehen und testen, ob die Zeichenfolge ein Vielfaches der Blockgröße ist, bevor Sie sie verwenden, oder dass der Rest der Zeichenfolge nicht zurückgegeben wird.
CodeMonkeyKing

Die Frage des OP macht nicht klar, ob er diese Funktionalität benötigt. Die erste Lösung ist einfacher, schneller und schlägt mit einer Ausnahme zuverlässig fehl, wenn die Zeichenfolge nicht gleichmäßig in die angegebene Blockgröße aufgeteilt werden kann. Ich bin damit einverstanden, dass die Rückgabe "falscher" Ergebnisse schlecht wäre, aber nicht das, was sie bewirkt - es wird nur eine Ausnahme ausgelöst, sodass ich sie verwenden kann, wenn Sie mit der Einschränkung leben können.
Eamon Nerbonne

56

Warum nicht Loops? Hier ist etwas, das es ganz gut machen würde:

        string str = "111122223333444455";
        int chunkSize = 4;
        int stringLength = str.Length;
        for (int i = 0; i < stringLength ; i += chunkSize)
        {
            if (i + chunkSize > stringLength) chunkSize = stringLength  - i;
            Console.WriteLine(str.Substring(i, chunkSize));

        }
        Console.ReadLine();

Ich weiß nicht, wie Sie mit dem Fall umgehen würden, in dem die Zeichenfolge nicht den Faktor 4 hat, aber nicht zu sagen, dass Ihre Idee nicht möglich ist. Ich frage mich nur, warum eine einfache for-Schleife dies sehr gut macht. Offensichtlich könnte das Obige gereinigt und sogar als Erweiterungsmethode eingesetzt werden.

Oder wie in den Kommentaren erwähnt, wissen Sie, dass es dann / 4 ist

str = "1111222233334444";
for (int i = 0; i < stringLength; i += chunkSize) 
  {Console.WriteLine(str.Substring(i, chunkSize));} 

1
Sie können int chunkSize = 4außerhalb der Schleife ziehen. Es wird nur beim letzten Durchgang geändert.
John Feminella

+1 für eine einfache und effektive Lösung - so hätte ich es gemacht, obwohl ich i += chunkSizestattdessen verwendet hätte.
Ian Kemp

Wahrscheinlich ein kleiner Streitpunkt, aber Sie sollten wahrscheinlich auch den str.Lengthaus der Schleife heraus und in eine lokale Variable ziehen. Der C # -Optimierer kann möglicherweise die Array-Länge inline setzen, aber ich denke, dass der geschriebene Code einen Methodenaufruf für jede Schleife ausführt, was nicht effizient ist, da sich die Größe von strnie ändert.
Daniel Pryden

@ Daniel, stell deine Idee da rein. Ich bin mir zwar nicht sicher, ob dies zur Laufzeit nicht berechnet werden würde, aber das ist eine andere Frage;)
Taube

@ Daniel kommt darauf zurück, ziemlich sicher, dass diese Optimierung vom Compiler extrahiert wird.
Taube

41

Mit regulären Ausdrücken und Linq :

List<string> groups = (from Match m in Regex.Matches(str, @"\d{4}")
                       select m.Value).ToList();

Ich finde das besser lesbar, aber es ist nur eine persönliche Meinung. Es kann auch ein Einzeiler sein :).


7
Ändern Sie das Muster in @ "\ d {1,4}" und es funktioniert für jede Zeichenfolgenlänge. :)
Guffa

3
+1 Obwohl dies langsamer ist als die anderen Lösungen, ist es definitiv sehr gut lesbar. Mir ist nicht klar, ob das OP Ziffern oder beliebige Zeichen erfordert. Es wäre wahrscheinlich ratsam, die \dZeichenklasse durch ein .und zu ersetzen und anzugeben RegexOptions.Singleline.
Eamon Nerbonne

2
oder einfach Regex.Matches (s, @ "\ d {1,4}"). Select (m => m.Value) .ToList (); Ich habe nie den Sinn dieser alternativen Syntax verstanden, die nur dazu dient, zu verschleiern, dass wir Erweiterungsmethoden verwenden.
The Dag

38

Dies basiert auf der @ dove-Lösung jedoch als Erweiterungsmethode implementiert.

Leistungen:

  • Erweiterungsmethode
  • Deckt Eckfälle ab
  • Teilt die Zeichenfolge mit beliebigen Zeichen: Zahlen, Buchstaben, andere Symbole

Code

public static class EnumerableEx
{    
    public static IEnumerable<string> SplitBy(this string str, int chunkLength)
    {
        if (String.IsNullOrEmpty(str)) throw new ArgumentException();
        if (chunkLength < 1) throw new ArgumentException();

        for (int i = 0; i < str.Length; i += chunkLength)
        {
            if (chunkLength + i > str.Length)
                chunkLength = str.Length - i;

            yield return str.Substring(i, chunkLength);
        }
    }
}

Verwendung

var result = "bobjoecat".SplitBy(3); // bob, joe, cat

Unit-Tests wurden der Kürze halber entfernt (siehe vorherige Überarbeitung )


Interessante Lösung, aber um Überprüfungen der Eingabe über Null hinaus zu vermeiden, erscheint es logischer, einer leeren Zeichenfolge zu erlauben, nur einen einzelnen Teil einer leeren Zeichenfolge zurückzugeben:if (str.Length == 0) yield return String.Empty; else { for... }
Nyerguds

Ich meine, so behandelt der normale String.Split leere Strings; Es wird ein Eintrag mit leeren Zeichenfolgen zurückgegeben.
Nyerguds

Randnotiz: Ihr Anwendungsbeispiel ist falsch. Sie können nicht einfach IEnumerablein ein Array umwandeln, insbesondere nicht implizit.
Nyerguds

Ich persönlich nenne diese Methode gerne Chunkify. Es ist nicht meine, ich erinnere mich nicht, wo ich diesen Namen gesehen habe, aber es fühlte sich sehr gut für mich an
quetzalcoatl

20

Wie ist das für einen Einzeiler?

List<string> result = new List<string>(Regex.Split(target, @"(?<=\G.{4})", RegexOptions.Singleline));

Bei dieser Regex spielt es keine Rolle, ob der letzte Block weniger als vier Zeichen enthält, da immer nur die Zeichen dahinter angezeigt werden.

Ich bin mir sicher, dass dies nicht die effizienteste Lösung ist, aber ich musste sie einfach rauswerfen.


in diesem Fall wird target.Lenght % ChunckSize == 0eine zusätzliche leere Zeile zurückgegeben, z. B.List<string> result = new List<string>(Regex.Split("fooo", @"(?<=\G.{4})", RegexOptions.Singleline));
fubo

9

Es ist nicht schön und es ist nicht schnell, aber es funktioniert, es ist ein Einzeiler und es ist LINQy:

List<string> a = text.Select((c, i) => new { Char = c, Index = i }).GroupBy(o => o.Index / 4).Select(g => new String(g.Select(o => o.Char).ToArray())).ToList();

Ist garantiert, dass GroupBy die Reihenfolge der Elemente beibehält?
Konstantin Spirin

ToCharArrayist unnötig da stringist IEnumerable<char>.
Juharr

8

Ich musste kürzlich etwas schreiben, das dies bei der Arbeit erreicht, also dachte ich, ich würde meine Lösung für dieses Problem veröffentlichen. Als zusätzlichen Bonus bietet die Funktionalität dieser Lösung eine Möglichkeit, die Zeichenfolge in die entgegengesetzte Richtung zu teilen, und sie verarbeitet Unicode-Zeichen korrekt, wie zuvor von Marvin Pinto erwähnt. Hier ist es also:

using System;
using Extensions;

namespace TestCSharp
{
    class Program
    {
        static void Main(string[] args)
        {    
            string asciiStr = "This is a string.";
            string unicodeStr = "これは文字列です。";

            string[] array1 = asciiStr.Split(4);
            string[] array2 = asciiStr.Split(-4);

            string[] array3 = asciiStr.Split(7);
            string[] array4 = asciiStr.Split(-7);

            string[] array5 = unicodeStr.Split(5);
            string[] array6 = unicodeStr.Split(-5);
        }
    }
}

namespace Extensions
{
    public static class StringExtensions
    {
        /// <summary>Returns a string array that contains the substrings in this string that are seperated a given fixed length.</summary>
        /// <param name="s">This string object.</param>
        /// <param name="length">Size of each substring.
        ///     <para>CASE: length &gt; 0 , RESULT: String is split from left to right.</para>
        ///     <para>CASE: length == 0 , RESULT: String is returned as the only entry in the array.</para>
        ///     <para>CASE: length &lt; 0 , RESULT: String is split from right to left.</para>
        /// </param>
        /// <returns>String array that has been split into substrings of equal length.</returns>
        /// <example>
        ///     <code>
        ///         string s = "1234567890";
        ///         string[] a = s.Split(4); // a == { "1234", "5678", "90" }
        ///     </code>
        /// </example>            
        public static string[] Split(this string s, int length)
        {
            System.Globalization.StringInfo str = new System.Globalization.StringInfo(s);

            int lengthAbs = Math.Abs(length);

            if (str == null || str.LengthInTextElements == 0 || lengthAbs == 0 || str.LengthInTextElements <= lengthAbs)
                return new string[] { str.ToString() };

            string[] array = new string[(str.LengthInTextElements % lengthAbs == 0 ? str.LengthInTextElements / lengthAbs: (str.LengthInTextElements / lengthAbs) + 1)];

            if (length > 0)
                for (int iStr = 0, iArray = 0; iStr < str.LengthInTextElements && iArray < array.Length; iStr += lengthAbs, iArray++)
                    array[iArray] = str.SubstringByTextElements(iStr, (str.LengthInTextElements - iStr < lengthAbs ? str.LengthInTextElements - iStr : lengthAbs));
            else // if (length < 0)
                for (int iStr = str.LengthInTextElements - 1, iArray = array.Length - 1; iStr >= 0 && iArray >= 0; iStr -= lengthAbs, iArray--)
                    array[iArray] = str.SubstringByTextElements((iStr - lengthAbs < 0 ? 0 : iStr - lengthAbs + 1), (iStr - lengthAbs < 0 ? iStr + 1 : lengthAbs));

            return array;
        }
    }
}

Hier ist auch ein Bildlink zu den Ergebnissen der Ausführung dieses Codes: http://i.imgur.com/16Iih.png


1
Ich habe ein Problem mit diesem Code festgestellt. Sie haben {str.ToString()}am Ende Ihrer ersten IF-Anweisung. Bist du sicher, dass du es nicht so gemeint hast str.String? Ich hatte ein Problem mit dem obigen Code, nahm diese Änderung vor und alles funktionierte.
gunr2171

@ gunr2171 Wenn str == null ist, gibt diese Zeile anscheinend auch eine NullReferenceException aus.
John Zabroski

5

Dies sollte viel schneller und effizienter sein als die Verwendung von LINQ oder anderen hier verwendeten Ansätzen.

public static IEnumerable<string> Splice(this string s, int spliceLength)
{
    if (s == null)
        throw new ArgumentNullException("s");
    if (spliceLength < 1)
        throw new ArgumentOutOfRangeException("spliceLength");

    if (s.Length == 0)
        yield break;
    var start = 0;
    for (var end = spliceLength; end < s.Length; end += spliceLength)
    {
        yield return s.Substring(start, spliceLength);
        start = end;
    }
    yield return s.Substring(start);
}

Dies sieht so aus, als würde es frühzeitig überprüft, aber nicht. Sie erhalten keine Fehlermeldung, bis Sie mit der Aufzählung der Aufzählungszeichen beginnen. Sie müssen Ihre Funktion in zwei Teile aufteilen, wobei der erste Teil die Argumentprüfung durchführt und dann die Ergebnisse des zweiten privaten Teils zurückgibt , der die Aufzählung durchführt.
ErikE

4
public static IEnumerable<IEnumerable<T>> SplitEvery<T>(this IEnumerable<T> values, int n)
{
    var ls = values.Take(n);
    var rs = values.Skip(n);
    return ls.Any() ?
        Cons(ls, SplitEvery(rs, n)) : 
        Enumerable.Empty<IEnumerable<T>>();
}

public static IEnumerable<T> Cons<T>(T x, IEnumerable<T> xs)
{
    yield return x;
    foreach (var xi in xs)
        yield return xi;
}

4

Sie können morelinq von Jon Skeet verwenden. Verwenden Sie Batch wie:

string str = "1111222233334444";
int chunkSize = 4;
var chunks = str.Batch(chunkSize).Select(r => new String(r.ToArray()));

Dies gibt 4 Chunks für die Zeichenfolge zurück "1111222233334444". Wenn die Zeichenfolgenlänge kleiner oder gleich der Blockgröße ist, Batchwird die Zeichenfolge als einziges Element von zurückgegebenIEnumerable<string>

Für die Ausgabe:

foreach (var chunk in chunks)
{
    Console.WriteLine(chunk);
}

und es wird geben:

1111
2222
3333
4444

Unter den Autoren von MoreLINQ sehe ich Jonathan Skeet , aber keinen Jon Skeet . Also meintest du den Jon Skeet oder was? ;-)
Sнаđошƒаӽ

3
static IEnumerable<string> Split(string str, double chunkSize)
{
    return Enumerable.Range(0, (int) Math.Ceiling(str.Length/chunkSize))
       .Select(i => new string(str
           .Skip(i * (int)chunkSize)
           .Take((int)chunkSize)
           .ToArray()));
}

und ein anderer Ansatz:

using System;
using System.Collections.Generic;
using System.Linq;

public class Program
{
    public static void Main()
    {

        var x = "Hello World";
        foreach(var i in x.ChunkString(2)) Console.WriteLine(i);
    }
}

public static class Ext{
    public static IEnumerable<string> ChunkString(this string val, int chunkSize){
        return val.Select((x,i) => new {Index = i, Value = x})
                  .GroupBy(x => x.Index/chunkSize, x => x.Value)
                  .Select(x => string.Join("",x));
    }
}

3

Sechs Jahre später o_O

Nur weil

    public static IEnumerable<string> Split(this string str, int chunkSize, bool remainingInFront)
    {
        var count = (int) Math.Ceiling(str.Length/(double) chunkSize);
        Func<int, int> start = index => remainingInFront ? str.Length - (count - index)*chunkSize : index*chunkSize;
        Func<int, int> end = index => Math.Min(str.Length - Math.Max(start(index), 0), Math.Min(start(index) + chunkSize - Math.Max(start(index), 0), chunkSize));
        return Enumerable.Range(0, count).Select(i => str.Substring(Math.Max(start(i), 0),end(i)));
    }

oder

    private static Func<bool, int, int, int, int, int> start = (remainingInFront, length, count, index, size) =>
        remainingInFront ? length - (count - index) * size : index * size;

    private static Func<bool, int, int, int, int, int, int> end = (remainingInFront, length, count, index, size, start) =>
        Math.Min(length - Math.Max(start, 0), Math.Min(start + size - Math.Max(start, 0), size));

    public static IEnumerable<string> Split(this string str, int chunkSize, bool remainingInFront)
    {
        var count = (int)Math.Ceiling(str.Length / (double)chunkSize);
        return Enumerable.Range(0, count).Select(i => str.Substring(
            Math.Max(start(remainingInFront, str.Length, count, i, chunkSize), 0),
            end(remainingInFront, str.Length, count, i, chunkSize, start(remainingInFront, str.Length, count, i, chunkSize))
        ));
    }

AFAIK alle Randfälle werden behandelt.

Console.WriteLine(string.Join(" ", "abc".Split(2, false))); // ab c
Console.WriteLine(string.Join(" ", "abc".Split(2, true))); // a bc
Console.WriteLine(string.Join(" ", "a".Split(2, true))); // a
Console.WriteLine(string.Join(" ", "a".Split(2, false))); // a

Was ist mit dem Randfall "Eingabe ist eine leere Zeichenfolge"? Ich würde erwarten, dass genau wie bei Split eine IEnumerable mit einer einzelnen leeren Zeichenfolge zurückgegeben wird, die einen Eintrag enthält.
Nyerguds

3

Einfach und kurz:

// this means match a space or not a space (anything) up to 4 characters
var lines = Regex.Matches(str, @"[\s\S]{0,4}").Cast<Match>().Select(x => x.Value);

Warum nicht verwenden .?
Marsze

3
static IEnumerable<string> Split(string str, int chunkSize)
{
   IEnumerable<string> retVal = Enumerable.Range(0, str.Length / chunkSize)
        .Select(i => str.Substring(i * chunkSize, chunkSize))

   if (str.Length % chunkSize > 0)
        retVal = retVal.Append(str.Substring(str.Length / chunkSize * chunkSize, str.Length % chunkSize));

   return retVal;
}

Die Länge der Eingabezeichenfolge, die nicht durch chunkSize teilbar ist, wird korrekt behandelt.

Bitte beachten Sie, dass möglicherweise zusätzlicher Code erforderlich ist, um Kantenfälle ordnungsgemäß zu behandeln (null oder leere Eingabezeichenfolge, chunkSize == 0).


2

Ein wichtiger Tipp, wenn die Zeichenfolge, die aufgeteilt wird, alle Unicode-Zeichen unterstützen muss.

Wenn die Zeichenfolge internationale Zeichen wie unterstützen soll 𠀋, teilen Sie die Zeichenfolge mithilfe der System.Globalization.StringInfo-Klasse auf. Mit StringInfo können Sie die Zeichenfolge basierend auf der Anzahl der Textelemente aufteilen.

string internationalString = '𠀋';

Die obige Zeichenfolge hat eine Länge von 2, da die String.LengthEigenschaft in diesem Fall die Anzahl der Char-Objekte und nicht die Anzahl der Unicode-Zeichen zurückgibt.


2

Beste, einfachste und allgemeinste Antwort :).

    string originalString = "1111222233334444";
    List<string> test = new List<string>();
    int chunkSize = 4; // change 4 with the size of strings you want.
    for (int i = 0; i < originalString.Length; i = i + chunkSize)
    {
        if (originalString.Length - i >= chunkSize)
            test.Add(originalString.Substring(i, chunkSize));
        else
            test.Add(originalString.Substring(i,((originalString.Length - i))));
    }

Die Berechnung der Länge in der letzten Zeile ist redundant. Verwenden Sie einfach die SubstringÜberladung, für die der Parameter length nicht erforderlich ist originalString.Substring(i). Auch können Sie >statt >=in Ihrem Scheck verwenden.
Racil Hilan

@RacilHilan Ich werde die Codeänderungen mit Ihrem Vorschlag testen und die Antwort aktualisieren. Ich bin froh, dass jemand mit einem so guten Ruf Zeit hat, meinen Code zu überprüfen. :) Danke, Sandeep
Sandeep Kushwah

2

Persönlich bevorzuge ich meine Lösung :-)

Es behandelt:

  • Zeichenfolgenlängen, die ein Vielfaches der Blockgröße betragen.
  • Zeichenfolgenlängen, die NICHT ein Vielfaches der Blockgröße sind.
  • Zeichenfolgenlängen, die kleiner als die Blockgröße sind.
  • NULL und leere Zeichenfolgen (löst eine Ausnahme aus).
  • Blockgrößen kleiner als 1 (löst eine Ausnahme aus).

Es wird als Erweiterungsmethode implementiert und berechnet die Anzahl der Chunks, die zuvor generiert werden sollen. Der letzte Block wird überprüft, da er kürzer sein muss, falls die Textlänge kein Vielfaches ist. Sauber, kurz, leicht zu verstehen ... und funktioniert!

    public static string[] Split(this string value, int chunkSize)
    {
        if (string.IsNullOrEmpty(value)) throw new ArgumentException("The string cannot be null.");
        if (chunkSize < 1) throw new ArgumentException("The chunk size should be equal or greater than one.");

        int remainder;
        int divResult = Math.DivRem(value.Length, chunkSize, out remainder);

        int numberOfChunks = remainder > 0 ? divResult + 1 : divResult;
        var result = new string[numberOfChunks];

        int i = 0;
        while (i < numberOfChunks - 1)
        {
            result[i] = value.Substring(i * chunkSize, chunkSize);
            i++;
        }

        int lastChunkSize = remainder > 0 ? remainder : chunkSize;
        result[i] = value.Substring(i * chunkSize, lastChunkSize);

        return result;
    }

2
List<string> SplitString(int chunk, string input)
{
    List<string> list = new List<string>();
    int cycles = input.Length / chunk;

    if (input.Length % chunk != 0)
        cycles++;

    for (int i = 0; i < cycles; i++)
    {
        try
        {
            list.Add(input.Substring(i * chunk, chunk));
        }
        catch
        {
            list.Add(input.Substring(i * chunk));
        }
    }
    return list;
}

1
Ich mag diese Antwort sehr, aber vielleicht sollten Sie if ((i + 1) * chunk> = input.Length) anstelle von try / catch verwenden, da Ausnahmen für Ausnahmefälle gelten.
Nelsontruran

2

Ich denke, das ist eine direkte Antwort:

public static IEnumerable<string> Split(this string str, int chunkSize)
    {
        if(string.IsNullOrEmpty(str) || chunkSize<1)
            throw new ArgumentException("String can not be null or empty and chunk size should be greater than zero.");
        var chunkCount = str.Length / chunkSize + (str.Length % chunkSize != 0 ? 1 : 0);
        for (var i = 0; i < chunkCount; i++)
        {
            var startIndex = i * chunkSize;
            if (startIndex + chunkSize >= str.Length)
                yield return str.Substring(startIndex);
            else
                yield return str.Substring(startIndex, chunkSize);
        }
    }

Und es deckt Randfälle ab.


2

Ich weiß, dass die Frage Jahre alt ist, aber hier ist eine Rx-Implementierung. Es behandelt das length % chunkSize != 0Problem sofort:

   public static IEnumerable<string> Chunkify(this string input, int size)
        {
            if(size < 1)
                throw new ArgumentException("size must be greater than 0");

            return input.ToCharArray()
                .ToObservable()
                .Buffer(size)            
                .Select(x => new string(x.ToArray()))
                .ToEnumerable();
        }

1

Ich habe etwas auf Joãos Lösung aufgebaut. Was ich anders gemacht habe, ist, dass Sie in meiner Methode tatsächlich angeben können, ob Sie das Array mit den verbleibenden Zeichen zurückgeben möchten oder ob Sie sie abschneiden möchten, wenn die Endzeichen nicht mit Ihrer erforderlichen Blocklänge übereinstimmen. Ich denke, es ist ziemlich flexibel und das Code ist ziemlich einfach:

using System;
using System.Linq;
using System.Text.RegularExpressions;

namespace SplitFunction
{
    class Program
    {
        static void Main(string[] args)
        {
            string text = "hello, how are you doing today?";
            string[] chunks = SplitIntoChunks(text, 3,false);
            if (chunks != null)
            {
                chunks.ToList().ForEach(e => Console.WriteLine(e));
            }

            Console.ReadKey();
        }

        private static string[] SplitIntoChunks(string text, int chunkSize, bool truncateRemaining)
        {
            string chunk = chunkSize.ToString(); 
            string pattern = truncateRemaining ? ".{" + chunk + "}" : ".{1," + chunk + "}";

            string[] chunks = null;
            if (chunkSize > 0 && !String.IsNullOrEmpty(text))
                chunks = (from Match m in Regex.Matches(text,pattern)select m.Value).ToArray(); 

            return chunks;
        }     
    }
}

1
    public static List<string> SplitByMaxLength(this string str)
    {
        List<string> splitString = new List<string>();

        for (int index = 0; index < str.Length; index += MaxLength)
        {
            splitString.Add(str.Substring(index, Math.Min(MaxLength, str.Length - index)));
        }

        return splitString;
    }

Sie haben den Parameter MaxLength vergessen.
Nyerguds

1

Leicht geändert, um Teile zurückzugeben, deren Größe nicht gleich chunkSize ist

public static IEnumerable<string> Split(this string str, int chunkSize)
    {
        var splits = new List<string>();
        if (str.Length < chunkSize) { chunkSize = str.Length; }
        splits.AddRange(Enumerable.Range(0, str.Length / chunkSize).Select(i => str.Substring(i * chunkSize, chunkSize)));
        splits.Add(str.Length % chunkSize > 0 ? str.Substring((str.Length / chunkSize) * chunkSize, str.Length - ((str.Length / chunkSize) * chunkSize)) : string.Empty);
        return (IEnumerable<string>)splits;
    }

Nicht sicher , sehe ich die Verwendung von Back-Casting , dass Listan IEnumerable; Alles, was Sie tun müssen, ist, listenspezifische Funktionen auszublenden, die Sie möglicherweise verwenden möchten. Es gibt überhaupt keinen Nachteil, nur das zurückzugeben List.
Nyerguds

1

Ich kann mich nicht erinnern, wer mir das gegeben hat, aber es funktioniert großartig. Ich habe eine Reihe von Möglichkeiten getestet, um Aufzählbare Typen in Gruppen aufzuteilen. Die Nutzung wäre einfach so ...

List<string> Divided = Source3.Chunk(24).Select(Piece => string.Concat<char>(Piece)).ToList();

Der Erweiterungscode würde so aussehen ...

#region Chunk Logic
private class ChunkedEnumerable<T> : IEnumerable<T>
{
    class ChildEnumerator : IEnumerator<T>
    {
        ChunkedEnumerable<T> parent;
        int position;
        bool done = false;
        T current;


        public ChildEnumerator(ChunkedEnumerable<T> parent)
        {
            this.parent = parent;
            position = -1;
            parent.wrapper.AddRef();
        }

        public T Current
        {
            get
            {
                if (position == -1 || done)
                {
                    throw new InvalidOperationException();
                }
                return current;

            }
        }

        public void Dispose()
        {
            if (!done)
            {
                done = true;
                parent.wrapper.RemoveRef();
            }
        }

        object System.Collections.IEnumerator.Current
        {
            get { return Current; }
        }

        public bool MoveNext()
        {
            position++;

            if (position + 1 > parent.chunkSize)
            {
                done = true;
            }

            if (!done)
            {
                done = !parent.wrapper.Get(position + parent.start, out current);
            }

            return !done;

        }

        public void Reset()
        {
            // per http://msdn.microsoft.com/en-us/library/system.collections.ienumerator.reset.aspx
            throw new NotSupportedException();
        }
    }

    EnumeratorWrapper<T> wrapper;
    int chunkSize;
    int start;

    public ChunkedEnumerable(EnumeratorWrapper<T> wrapper, int chunkSize, int start)
    {
        this.wrapper = wrapper;
        this.chunkSize = chunkSize;
        this.start = start;
    }

    public IEnumerator<T> GetEnumerator()
    {
        return new ChildEnumerator(this);
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

}
private class EnumeratorWrapper<T>
{
    public EnumeratorWrapper(IEnumerable<T> source)
    {
        SourceEumerable = source;
    }
    IEnumerable<T> SourceEumerable { get; set; }

    Enumeration currentEnumeration;

    class Enumeration
    {
        public IEnumerator<T> Source { get; set; }
        public int Position { get; set; }
        public bool AtEnd { get; set; }
    }

    public bool Get(int pos, out T item)
    {

        if (currentEnumeration != null && currentEnumeration.Position > pos)
        {
            currentEnumeration.Source.Dispose();
            currentEnumeration = null;
        }

        if (currentEnumeration == null)
        {
            currentEnumeration = new Enumeration { Position = -1, Source = SourceEumerable.GetEnumerator(), AtEnd = false };
        }

        item = default(T);
        if (currentEnumeration.AtEnd)
        {
            return false;
        }

        while (currentEnumeration.Position < pos)
        {
            currentEnumeration.AtEnd = !currentEnumeration.Source.MoveNext();
            currentEnumeration.Position++;

            if (currentEnumeration.AtEnd)
            {
                return false;
            }

        }

        item = currentEnumeration.Source.Current;

        return true;
    }

    int refs = 0;

    // needed for dispose semantics 
    public void AddRef()
    {
        refs++;
    }

    public void RemoveRef()
    {
        refs--;
        if (refs == 0 && currentEnumeration != null)
        {
            var copy = currentEnumeration;
            currentEnumeration = null;
            copy.Source.Dispose();
        }
    }
}
/// <summary>Speed Checked.  Works Great!</summary>
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, int chunksize)
{
    if (chunksize < 1) throw new InvalidOperationException();

    var wrapper = new EnumeratorWrapper<T>(source);

    int currentPos = 0;
    T ignore;
    try
    {
        wrapper.AddRef();
        while (wrapper.Get(currentPos, out ignore))
        {
            yield return new ChunkedEnumerable<T>(wrapper, chunksize, currentPos);
            currentPos += chunksize;
        }
    }
    finally
    {
        wrapper.RemoveRef();
    }
}
#endregion

1
class StringHelper
{
    static void Main(string[] args)
    {
        string str = "Hi my name is vikas bansal and my email id is bansal.vks@gmail.com";
        int offSet = 10;

        List<string> chunks = chunkMyStr(str, offSet);

        Console.Read();
    }

    static List<string> chunkMyStr(string str, int offSet)
    {


        List<string> resultChunks = new List<string>();

        for (int i = 0; i < str.Length; i += offSet)
        {
            string temp = str.Substring(i, (str.Length - i) > offSet ? offSet : (str.Length - i));
            Console.WriteLine(temp);
            resultChunks.Add(temp);


        }

        return resultChunks;
    }
}

Sie können Ihren Code leicht verbessern: Verschieben Sie den Inkrementausdruck i += offSetin Ihren forAusdruck.
JimiLoe

1

Geändert (jetzt akzeptiert es alle nicht null stringund alle positiven chunkSize) Konstantin Spirins Lösung:

public static IEnumerable<String> Split(String value, int chunkSize) {
  if (null == value)
    throw new ArgumentNullException("value");
  else if (chunkSize <= 0)
    throw new ArgumentOutOfRangeException("chunkSize", "Chunk size should be positive");

  return Enumerable
    .Range(0, value.Length / chunkSize + ((value.Length % chunkSize) == 0 ? 0 : 1))
    .Select(index => (index + 1) * chunkSize < value.Length 
      ? value.Substring(index * chunkSize, chunkSize)
      : value.Substring(index * chunkSize));
}

Tests:

  String source = @"ABCDEF";

  // "ABCD,EF"
  String test1 = String.Join(",", Split(source, 4));
  // "AB,CD,EF"
  String test2 = String.Join(",", Split(source, 2));
  // "ABCDEF"
  String test3 = String.Join(",", Split(source, 123));

1
static List<string> GetChunks(string value, int chunkLength)
{
    var res = new List<string>();
    int count = (value.Length / chunkLength) + (value.Length % chunkLength > 0 ? 1 : 0);
    Enumerable.Range(0, count).ToList().ForEach(f => res.Add(value.Skip(f * chunkLength).Take(chunkLength).Select(z => z.ToString()).Aggregate((a,b) => a+b)));
    return res;
}

Demo


Dieser behält den Rest der Saite (nach dem Teilen), auch wenn er kürzer als "chunkLenght" ist, danke
Jason Loki Smith,

0

Basierend auf anderen Posterantworten, zusammen mit einigen Verwendungsbeispielen:

public static string FormatSortCode(string sortCode)
{
    return ChunkString(sortCode, 2, "-");
}
public static string FormatIBAN(string iban)
{
    return ChunkString(iban, 4, "&nbsp;&nbsp;");
}

private static string ChunkString(string str, int chunkSize, string separator)
{
    var b = new StringBuilder();
    var stringLength = str.Length;
    for (var i = 0; i < stringLength; i += chunkSize)
    {
        if (i + chunkSize > stringLength) chunkSize = stringLength - i;
        b.Append(str.Substring(i, chunkSize));
        if (i+chunkSize != stringLength)
            b.Append(separator);
    }
    return b.ToString();
}

0

Verwenden der Puffererweiterungen aus der IX-Bibliothek

    static IEnumerable<string> Split( this string str, int chunkSize )
    {
        return str.Buffer(chunkSize).Select(l => String.Concat(l));
    }
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.