Dzielenie sznurka na kawałki o określonym rozmiarze


218

Załóżmy, że mam ciąg:

string str = "1111222233334444"; 

Jak mogę rozbić ten sznur na kawałki o pewnym rozmiarze?

np. podzielenie tego na rozmiary 4 zwróci ciągi znaków:

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

18
Po co używać LINQ lub wyrażeń regularnych, gdy standardowe funkcje manipulacji ciągami w języku C # mogą to zrobić przy mniejszym wysiłku i większej szybkości? Co się też stanie, jeśli ciąg znaków będzie miał nieparzystą liczbę znaków?
Ian Kemp,

7
„Chciałbym unikać pętli” - dlaczego?
Mitch Wheat

12
Korzystanie z prostej pętli jest zdecydowanie tym, co zapewnia najlepszą wydajność.
Guffa,

4
nichesoftware.co.nz/blog/200909/linq-vs-loop-performance to całkiem dobre porównanie między linq a rzeczywistym zapętlaniem tablicy. Wątpię, czy kiedykolwiek znajdziesz linq szybciej niż ręcznie napisany kod, ponieważ ciągle wywołuje on delegatów w czasie wykonywania, których trudno zoptymalizować. Linq jest jednak zabawniejszy :)
Blindy,

2
Niezależnie od tego, czy używasz LINQ, czy wyrażeń regularnych, pętla nadal istnieje.
Anton Tykhyy,

Odpowiedzi:


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

Należy pamiętać, że może być wymagany dodatkowy kod, aby z wdziękiem obsługiwać przypadki na krawędziach ( nulllub pusty ciąg chunkSize == 0wejściowy, długość ciągu wejściowego nie jest podzielna przez chunkSizeitp.). Pierwotne pytanie nie określa żadnych wymagań dla tych przypadków skrajnych, a w rzeczywistości wymagania mogą się różnić, więc nie wchodzą w zakres tej odpowiedzi.


3
@Harry Dobry połów! Można temu zaradzić za pomocą wyrażenia trójskładnikowego parametru liczenia podłańcucha. Coś jak: (i * chunkSize + chunkSize <= str.Length) ? chunkSize : str.Length - i * chunkSize. Dodatkowym problemem jest to, że ta funkcja nie bierze pod uwagę, że str ma wartość NULL. To może być ustalona przez owinięcie całej instrukcji return w innym potrójnego wyrażeniem: (str != null) ? ... : Enumerable.Empty<String>();.
Drew Spickes

7
Było blisko, ale w przeciwieństwie do poprzednich 30 upvoterów, musiałem zmienić limit liczby pętli w zakresie od str.Length / chunkSizedodouble length = str.Length; double size = chunkSize; int count = (int)Math.Ceiling(length/size); return Enumerable.Range(0, count)...
odstępu

4
@KonstantinSpirin Zgadzam się, czy kod działał. Obsługuje tylko przypadek, gdy łańcuch jest wielokrotnością chunkSize, reszta łańcucha zostaje utracona. Proszę o poprawienie. Pamiętaj również, że LINQ i jego magia nie są tak łatwe do zrozumienia dla kogoś, kto chce po prostu znaleźć rozwiązanie tego problemu. Osoba musi teraz zrozumieć, co robią funkcje Enumerable.Range () i .Select (). Nie będę twierdził, że powinieneś to rozumieć, aby pisać kod C # / .NET, ponieważ te funkcje są w BCL od wielu lat.
CodeMonkeyKing

6
Autor tematu powiedział w komentarzach, że StringLength % 4 will always be 0. Jeśli Linqnie jest to tak łatwe do zrozumienia, istnieją inne odpowiedzi, które używają pętli i wydajności. Każdy może wybrać rozwiązanie, które najbardziej mu się podoba. Możesz opublikować swój kod jako odpowiedź, a ludzie chętnie będą na niego głosować.
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 Pietrow

135

W kombinacji odpowiedzi dove + Konstatin ...

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

Będzie to działać dla wszystkich ciągów, które można podzielić na całą liczbę porcji, i w przeciwnym razie wygeneruje wyjątek.

Jeśli chcesz obsługiwać ciągi dowolnej długości, możesz użyć następującego kodu:

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

Jednak PO wyraźnie stwierdził, że tego nie potrzebuje; jest nieco dłuższy i trudniejszy do odczytania, nieco wolniejszy. W duchu KISS i YAGNI wybrałbym pierwszą opcję: jest to prawdopodobnie najbardziej wydajna możliwa implementacja, jest bardzo krótka, czytelna i, co ważne, wprowadza wyjątek dla niezgodnych danych wejściowych.


4
+1 warte skinięcia głową. Trochę uderza w głowę. szuka zwięzłego sytnaxa, a ty dajesz (prawdopodobnie) lepszą wydajność.
dove

7
A jeśli sprawisz, że będzie „statyczny ... Chunk (ten ciąg str, int chunkSize) {” masz jeszcze jeden „nowy” C # -Feature. Następnie możesz napisać „1111222233334444” .Chunk (4).
MartinStettner,

1
@MartinStettner: To z pewnością dobry pomysł, jeśli jest to powszechna operacja.
Eamon Nerbonne,

Powinieneś dołączyć tylko ten drugi kod. Ten pierwszy wymaga, abyś zrozumiał i sprawdził, czy łańcuch jest wielokrotnością rozmiaru fragmentu przed użyciem, lub zrozumiał, że nie zwróci on pozostałej części łańcucha.
CodeMonkeyKing

Pytanie OP nie wyjaśnia, czy potrzebuje tej funkcjonalności. Pierwsze rozwiązanie jest prostsze, szybsze i niezawodnie zawodzi, z wyjątkiem tego, że łańcucha nie można równomiernie podzielić na określony rozmiar porcji. Zgadzam się, że zwracanie „złych” wyników byłoby złe, ale to nie to, co robi - po prostu rzuca wyjątek, więc byłbym w porządku, używając go, jeśli możesz żyć z ograniczeniami.
Eamon Nerbonne

56

Dlaczego nie pętle? Oto coś, co zrobiłoby to całkiem dobrze:

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

Nie wiem, jak poradziłbyś sobie z przypadkiem, w którym struna nie jest równa 4, ale nie mówię, że twój pomysł nie jest możliwy, zastanawiasz się, dlaczego jest to uzasadnione, jeśli prosta pętla for robi to bardzo dobrze? Oczywiście powyższe można oczyścić, a nawet zastosować jako metodę rozszerzenia.

Lub, jak wspomniano w komentarzach, wiesz, że to / 4

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

1
Możesz wyciągnąć int chunkSize = 4poza pętlę. Zostanie zmodyfikowany tylko przy ostatnim przejściu.
John Feminella,

+1 za proste i skuteczne rozwiązanie - tak bym to zrobił, chociaż użyłbym i += chunkSizetego.
Ian Kemp,

Prawdopodobnie drobna sprzeczka, ale prawdopodobnie powinieneś również wyciągnąć str.Lengthpętlę z pętli do zmiennej lokalnej. Optymalizator C # może być w stanie wstawić długość tablicy, ale myślę, że kod jak napisany wykona wywołanie metody w każdej pętli, co nie jest wydajne, ponieważ rozmiar strnigdy się nie zmienia.
Daniel Pryden,

@Daniel, umieść tam swój pomysł. choć nie jestem pewien, czy nie będzie to obliczane w czasie wykonywania, ale to kolejne pytanie;)
dove

@Daniel wraca do tego, całkiem pewien, że ta optymalizacja zostanie wyodrębniona przez kompilator.
dove

41

Używanie wyrażeń regularnych i Linq :

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

Uważam, że jest to bardziej czytelne, ale to tylko osobista opinia. Może to być również jedna linijka:).


7
Zmień wzorzec na @ "\ d {1,4}" i działa dla dowolnej długości łańcucha. :)
Guffa,

3
+1 Chociaż jest to wolniejsze niż inne rozwiązania, jest zdecydowanie bardzo czytelne. Nie jest dla mnie jasne, czy OP wymaga cyfr czy dowolnych znaków; prawdopodobnie rozsądnie byłoby zamienić \dklasę znaków na .i określić RegexOptions.Singleline.
Eamon Nerbonne,

2
lub po prostu Regex.Matches (s, @ "\ d {1,4}"). Wybierz (m => m.Value) .ToList (); Nigdy nie miałem sensu tej alternatywnej składni, która służy jedynie zaciemnieniu, że używamy metod rozszerzenia.
The Dag,

38

Jest to oparte na rozwiązaniu @dove, ale zaimplementowane jako metoda rozszerzenia.

Korzyści:

  • Metoda przedłużenia
  • Obejmuje skrzynki narożne
  • Dzieli ciąg znaków na dowolne znaki: cyfry, litery, inne symbole

Kod

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

Stosowanie

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

Testy jednostkowe zostały usunięte ze względu na zwięzłość (patrz poprzednia wersja )


Ciekawe rozwiązanie, ale ze względu na unikanie sprawdzania danych wejściowych poza wartością zerową wydaje się bardziej logiczne, aby pozwolić pustemu ciągowi zwracać tylko jedną część pustego ciągu:if (str.Length == 0) yield return String.Empty; else { for... }
Nyerguds

Mam na myśli, że tak normalny String.Split obsługuje puste ciągi; zwraca jeden pusty ciąg znaków.
Nyerguds,

Uwaga dodatkowa: przykład użycia jest niepoprawny. Nie możesz po prostu rzutować IEnumerablena tablicę, szczególnie niejawnie.
Nyerguds

Osobiście lubię nazywać tę metodę Chunkify. To nie moja, nie pamiętam, gdzie widziałem tę nazwę, ale było mi bardzo przyjemnie
quetzalcoatl

20

Jak to w przypadku jednej linijki?

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

Z tym wyrażeniem regularnym nie ma znaczenia, czy ostatni fragment ma mniej niż cztery znaki, ponieważ zawsze patrzy tylko na znaki za nim.

Jestem pewien, że nie jest to najskuteczniejsze rozwiązanie, ale musiałem to po prostu rzucić.


w przypadku target.Lenght % ChunckSize == 0tego zwraca dodatkowy pusty wiersz np.List<string> result = new List<string>(Regex.Split("fooo", @"(?<=\G.{4})", RegexOptions.Singleline));
fubo

9

Nie jest ładny i nie jest szybki, ale działa, jest jednowarstwowy i jest 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();

Czy jest zagwarantowane, że GroupBy zachowuje porządek elementów?
Konstantin Spirin

ToCharArrayjest niepotrzebne, ponieważ stringjest IEnumerable<char>.
juharr

8

Niedawno musiałem napisać coś, co to osiąga w pracy, więc pomyślałem, że opublikuję moje rozwiązanie tego problemu. Jako dodatkowy bonus, funkcjonalność tego rozwiązania zapewnia sposób na podzielenie łańcucha w przeciwnym kierunku i poprawnie obsługuje znaki Unicode, jak wcześniej wspomniano powyżej Marvin Pinto. Oto on:

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

Ponadto znajduje się link do obrazu z wynikami uruchomienia tego kodu: http://i.imgur.com/16Iih.png


1
Zauważyłem problem z tym kodem. Masz {str.ToString()}na końcu pierwszego wyciągu IF. Jesteś pewien, że nie miałeś na myśli str.String? Miałem problem z powyższym kodem, wprowadziłem tę zmianę i wszystko działało.
gunr2171

@ gunr2171 Wygląda na to, że jeśli str == null, linia ta również da wyjątek NullReferenceException.
John Zabroski

5

Powinno to być znacznie szybsze i bardziej wydajne niż używanie LINQ lub innych stosowanych tutaj podejść.

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

To wygląda tak jak to robi wczesne sprawdzenie, ale tak nie jest. Nie pojawia się błąd, dopóki nie zaczniesz wyliczać wyliczenia. Musisz podzielić swoją funkcję na dwie części, gdzie pierwsza część sprawdza argument, a następnie zwraca wyniki drugiej, prywatnej części, która wykonuje wyliczenie.
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

Możesz użyć morelinq Jona Skeeta. Użyj partii jak:

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

Zwróci 4 porcje dla łańcucha "1111222233334444". Jeśli długość ciągu jest mniejsza lub równa rozmiarowi fragmentu Batch, zwróci ciąg jako jedyny elementIEnumerable<string>

Do wyjścia:

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

i da:

1111
2222
3333
4444

Wśród autorów MoreLINQ widzę Jonathana Skeeta , ale nie ma Jona Skeeta . Tak pan myśli o Jon Skeet, czy co? ;-)
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()));
}

i inne podejście:

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

Sześć lat później o_O

Właśnie dlatego

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

lub

    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 obsługiwane są wszystkie skrzynki krawędziowe.

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

Co z przypadkowym przypadkiem „wejściowym jest pusty ciąg”? Spodziewałbym się, że tak jak w przypadku Split, zwróci IEnumerable z pojedynczym pustym ciągiem zawierającym wpis.
Nyerguds,

3

Prosty i krótki:

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

Dlaczego nie użyć .?
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;
}

Prawidłowo obsługuje długość ciągu wejściowego niepodzielnego przez chunkSize.

Należy pamiętać, że może być wymagany dodatkowy kod, aby z wdziękiem obsługiwać przypadki na krawędziach (zerowy lub pusty ciąg wejściowy, chunkSize == 0).


2

Ważna wskazówka, jeśli fragmentowany ciąg musi obsługiwać wszystkie znaki Unicode.

Jeśli ciąg ma obsługiwać znaki międzynarodowe, takie jak 𠀋, podziel go przy użyciu klasy System.Globalization.StringInfo. Za pomocą StringInfo możesz podzielić ciąg znaków na podstawie liczby elementów tekstowych.

string internationalString = '𠀋';

Powyższy ciąg ma długość 2, ponieważ String.Lengthwłaściwość zwraca w tym przypadku liczbę obiektów Char, a nie liczbę znaków Unicode.


2

Najlepsza, najprostsza i ogólna odpowiedź :).

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

Obliczanie długości w ostatnim wierszu jest zbędne, wystarczy użyć Substringprzeciążenia, które nie wymaga parametru długości originalString.Substring(i). Możesz także użyć >zamiast >=czeku.
Racil Hilan,

@RacilHilan Przetestuję zmiany kodu według Twojej sugestii i zaktualizuję odpowiedź. Cieszę się, że ktoś o tak dobrej reputacji miał czas na sprawdzenie mojego kodu. :) Dzięki, Sandeep
Sandeep Kushwah,

2

Osobiście wolę moje rozwiązanie :-)

Obsługuje:

  • Długości łańcucha będące wielokrotnością wielkości porcji.
  • Długości łańcucha, które NIE są wielokrotnością wielkości porcji.
  • Długości łańcucha mniejsze niż wielkość porcji.
  • NULL i puste ciągi znaków (zgłasza wyjątek).
  • Wielkość porcji mniejsza niż 1 (zgłasza wyjątek).

Został zaimplementowany jako metoda rozszerzenia i wcześniej oblicza liczbę porcji, które zostaną wygenerowane. Sprawdza ostatnią porcję, ponieważ jeśli długość tekstu nie jest wielokrotnością, musi być krótsza. Czysty, krótki, łatwy do zrozumienia ... i działa!

    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
Bardzo podoba mi się ta odpowiedź, ale może powinieneś użyć if ((i + 1) * chunk> = input.Length) zamiast try / catch, ponieważ wyjątki są wyjątkowe.
nelsontruran

2

Myślę, że jest to prosta odpowiedź:

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

I obejmuje skrzynki krawędziowe.


2

Wiem, że pytanie ma już lata, ale tutaj jest implementacja Rx. Rozwiązuje length % chunkSize != 0problem po wyjęciu z pudełka:

   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

Lekko rozbudowałem rozwiązanie João. To, co zrobiłem inaczej, jest w mojej metodzie, możesz właściwie określić, czy chcesz zwrócić tablicę z pozostałymi znakami, czy też chcesz je obciąć, jeśli znaki końcowe nie pasują do wymaganej długości fragmentu, myślę, że jest dość elastyczny, a kod jest dość prosty:

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

Zapomniałeś parametru MaxLength.
Nyerguds,

1

Zmieniono nieznacznie, aby zwracać części, których rozmiar nie jest równy chunkSize

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

Nie wiesz, widzę użycie retrospektywną że Listdo IEnumerable; wystarczy ukryć funkcje specyficzne dla listy, których możesz chcieć użyć. Nie ma żadnej wady po prostu zwrot List.
Nyerguds,

1

Nie pamiętam, kto mi to dał, ale działa świetnie. Szybko przetestowałem kilka sposobów na podzielenie typów policzalnych na grupy. Użycie byłoby po prostu takie ...

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

Kod rozszerzenia wyglądałby tak ...

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

Możesz nieznacznie ulepszyć kod: przenieś wyrażenie przyrostowe i += offSetna swoje forwyrażenie.
JimiLoe,

1

Zmodyfikowany (obecnie przyjmuje żadnej non NULL stringi dowolną dodatnią chunkSize) Konstantin Spirin „s rozwiązanie:

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

Testy:

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

próbny


ten zachowuje resztę ciągu (po podziale), nawet jeśli jest krótszy niż „chunkLenght”, dzięki
Jason Loki Smith

0

W oparciu o odpowiedzi innych plakatów oraz kilka przykładów użycia:

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

Korzystanie z rozszerzeń bufora z biblioteki IX

    static IEnumerable<string> Split( this string str, int chunkSize )
    {
        return str.Buffer(chunkSize).Select(l => String.Concat(l));
    }
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.