Memisahkan string menjadi potongan dengan ukuran tertentu

218

Misalkan saya punya string:

string str = "1111222233334444"; 

Bagaimana saya bisa memecah string ini menjadi beberapa ukuran?

misalnya, memecah ini menjadi ukuran 4 akan menghasilkan string:

"1111"
"2222"
"3333"
"4444"
Jeff Mercado
sumber
18
Mengapa menggunakan LINQ atau regex ketika fungsi manipulasi string standar C # dapat melakukan ini dengan sedikit usaha dan lebih banyak kecepatan? Juga, apa yang terjadi jika string memiliki jumlah karakter ganjil?
Ian Kemp
7
"Saya ingin menghindari loop" - mengapa?
Mitch Wheat
12
Menggunakan loop sederhana jelas merupakan hal yang memberikan kinerja terbaik.
Guffa
4
nichesoftware.co.nz/blog/200909/linq-vs-loop-performance adalah perbandingan yang cukup bagus antara linq dan perulangan aktual pada array. Saya ragu Anda akan menemukan linq lebih cepat daripada kode yang ditulis secara manual karena terus memanggil delegasi run-time yang sulit untuk dioptimalkan. Linq lebih menyenangkan :) :)
Blindy
2
Apakah Anda menggunakan LINQ atau regex, loop masih ada.
Anton Tykhyy

Jawaban:

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

Harap dicatat bahwa kode tambahan mungkin diperlukan untuk menangani case edge dengan anggun ( nullatau mengosongkan string input,, chunkSize == 0panjang string input tidak dapat dibagi oleh chunkSize, dll.). Pertanyaan asli tidak menentukan persyaratan untuk kasus tepi ini dan dalam kehidupan nyata persyaratan mungkin bervariasi sehingga mereka berada di luar cakupan jawaban ini.

Konstantin Spirin
sumber
3
@Harry, tangkapan bagus! Ini dapat diperbaiki dengan ekspresi ter-drop-in pada parameter count dari substring. Sesuatu seperti: (i * chunkSize + chunkSize <= str.Length) ? chunkSize : str.Length - i * chunkSize. Masalah tambahan adalah bahwa fungsi ini tidak memperhitungkan str menjadi nol. Ini bisa diperbaiki dengan membungkus pernyataan kembali seluruh dalam ekspresi terner lain: (str != null) ? ... : Enumerable.Empty<String>();.
Drew Spickes
7
Ini dekat, tetapi tidak seperti 30 upvoter sebelumnya, saya harus mengubah batas jumlah loop Range dari str.Length / chunkSizekedouble length = str.Length; double size = chunkSize; int count = (int)Math.Ceiling(length/size); return Enumerable.Range(0, count)...
gap
4
@KonstantinSpirin Saya setuju jika kodenya bekerja. Ini hanya menangani case di mana string adalah kelipatan dari chunkSize, sisa string hilang. Tolong ubah. Perlu diingat juga bahwa LINQ dan sulapnya tidak mudah dipahami oleh seseorang yang hanya ingin melihat solusi untuk masalah ini. Seseorang sekarang harus memahami apa yang dilakukan fungsi Enumerable.Range () dan .Select (). Saya tidak akan berdebat bahwa Anda harus memiliki pemahaman tentang itu untuk menulis kode C # /. NET karena fungsi-fungsi ini telah di BCL selama bertahun-tahun sekarang.
CodeMonkeyKing
6
Topik pemula mengatakan dalam komentar itu StringLength % 4 will always be 0. Jika Linqtidak mudah dipahami maka ada jawaban lain yang menggunakan loop dan hasil. Siapa pun bebas memilih solusi yang paling disukainya. Anda dapat memposting kode Anda sebagai jawaban dan orang-orang akan dengan senang hati memilihnya.
Konstantin Spirin
3
Enumerable.Range (0, (str.Length + chunkSize - 1) / chunkSize) .Pilih (i => str.Substring (i * chunkSize, Math.Min (str.Length - i * chunkSize, chunkSize))
Sten Petrov
135

Dalam kombinasi jawaban 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);
}

Ini akan bekerja untuk semua string yang dapat dipecah menjadi sejumlah besar potongan, dan akan melemparkan pengecualian.

Jika Anda ingin mendukung string dengan panjang apa pun, Anda dapat menggunakan kode berikut:

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

Namun, OP secara eksplisit menyatakan dia tidak membutuhkan ini; itu agak lebih panjang dan sulit dibaca, sedikit lebih lambat. Dalam semangat KISS dan YAGNI, saya akan memilih opsi pertama: itu mungkin implementasi yang seefisien mungkin, dan sangat singkat, mudah dibaca, dan, yang penting, melempar pengecualian untuk input yang tidak sesuai.

Eamon Nerbonne
sumber
4
+1 bernilai anggukan. agak memukul paku di kepala. dia mencari sytnax ringkas dan Anda juga memberikan (mungkin) kinerja yang lebih baik.
dove
7
Dan jika Anda menjadikannya "static ... Chunk (str ini, int chunkSize) {" Anda bahkan punya satu C "baru" lagi -Fitur di dalamnya. Maka Anda dapat menulis "1111222233334444" .Chunk (4).
MartinStettner
1
@ MartinStettner: Itu tentu ide yang bagus jika ini adalah operasi yang umum.
Eamon Nerbonne
Anda hanya harus memasukkan kode yang terakhir. Yang pertama mengharuskan Anda memahami dan menguji untuk string menjadi kelipatan dari ukuran chunk sebelum menggunakan, atau memahami bahwa string tidak akan mengembalikan sisa string.
CodeMonkeyKing
Pertanyaan OP tidak menjelaskan apakah dia membutuhkan fungsionalitas itu. Solusi pertama lebih sederhana, lebih cepat dan andal gagal dengan pengecualian jika string tidak dapat dibagi secara merata ke ukuran chunk yang ditentukan. Saya setuju bahwa mengembalikan hasil "salah" akan menjadi buruk, tetapi bukan itu yang dilakukannya - itu hanya melempar pengecualian, jadi saya akan setuju jika menggunakannya jika Anda dapat hidup dengan batasan.
Eamon Nerbonne
56

Kenapa tidak loop? Inilah sesuatu yang akan melakukannya dengan cukup baik:

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

Saya tidak tahu bagaimana Anda akan berurusan dengan kasus di mana string bukan faktor 4, tetapi tidak mengatakan Anda ide tidak mungkin, hanya bertanya-tanya motivasi untuk itu jika sederhana untuk loop melakukannya dengan sangat baik? Jelas di atas bisa dibersihkan dan bahkan dimasukkan sebagai metode perpanjangan.

Atau seperti yang disebutkan dalam komentar, Anda tahu itu / 4 lalu

str = "1111222233334444";
for (int i = 0; i < stringLength; i += chunkSize) 
  {Console.WriteLine(str.Substring(i, chunkSize));} 
merpati
sumber
1
Anda dapat menarik int chunkSize = 4keluar dari loop. Itu hanya akan dimodifikasi pada pass terakhir.
John Feminella
Memberi +1 untuk solusi sederhana dan efektif - ini adalah bagaimana saya akan melakukannya, meskipun saya akan menggunakannya i += chunkSizesebagai gantinya.
Ian Kemp
Mungkin sedikit berdalih, tetapi Anda mungkin juga harus menarik str.Lengthkeluar dari loop dan ke dalam variabel lokal. Pengoptimal C # mungkin dapat inline panjang array, tapi saya pikir kode seperti yang tertulis akan melakukan pemanggilan metode pada setiap loop, yang tidak efisien, karena ukuran strtidak pernah berubah.
Daniel Pryden
@Aniel, taruh idemu di sana. meskipun saya tidak yakin bahwa ini tidak akan dihitung pada saat runtime, tapi itu pertanyaan lain;)
dove
@Daniel kembali ke sini, cukup yakin bahwa optimasi ini akan diekstraksi oleh kompiler.
dove
41

Menggunakan ekspresi reguler dan Linq :

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

Saya menemukan ini lebih mudah dibaca, tetapi itu hanya pendapat pribadi. Ini juga bisa menjadi satu-baris:).

João Silva
sumber
7
Ubah pola menjadi @ "\ d {1,4}" dan itu berfungsi untuk panjang string apa pun. :)
Guffa
3
+1 Meskipun ini lebih lambat daripada solusi lain, itu pasti sangat mudah dibaca. Tidak jelas bagi saya apakah OP membutuhkan angka atau karakter yang berubah-ubah; mungkin akan bijaksana untuk mengganti \dkelas karakter dengan a .dan untuk menentukan RegexOptions.Singleline.
Eamon Nerbonne
2
atau hanya Regex.Matches (s, @ "\ d {1,4}"). Pilih (m => m.Value) .ToList (); Saya tidak pernah mendapatkan poin dari sintaks alternatif ini yang hanya berfungsi untuk mengaburkan bahwa kita menggunakan metode ekstensi.
The Dag
38

Ini didasarkan pada solusi @dove tetapi diimplementasikan sebagai metode ekstensi.

Manfaat:

  • Metode ekstensi
  • Meliputi kasing sudut
  • Pisahkan string dengan karakter apa pun: angka, huruf, simbol lainnya

Kode

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

Pemakaian

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

Tes unit dihapus karena singkatnya (lihat revisi sebelumnya )

oleksii
sumber
Solusi yang menarik, tetapi demi menghindari cek di luar nol pada input, tampaknya lebih logis untuk mengizinkan string kosong untuk hanya mengembalikan bagian string-kosong tunggal:if (str.Length == 0) yield return String.Empty; else { for... }
Nyerguds
Maksudku, begitulah String normal. Slit menangani string kosong; mengembalikan satu entri string kosong.
Nyergud
Catatan: contoh penggunaan Anda salah. Anda tidak bisa hanya menggunakan IEnumerablearray, terutama tidak secara implisit.
Nyerguds
Saya pribadi suka menyebut metode itu Chunkify .. Itu bukan milik saya, saya tidak ingat di mana saya melihat nama itu, tetapi rasanya sangat menyenangkan bagi saya
quetzalcoatl
20

Bagaimana ini untuk satu-liner?

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

Dengan regex ini tidak masalah jika potongan terakhir kurang dari empat karakter, karena hanya pernah melihat karakter di belakangnya.

Saya yakin ini bukan solusi yang paling efisien, tetapi saya harus membuangnya di sana.

Alan Moore
sumber
dalam kasus target.Lenght % ChunckSize == 0itu mengembalikan baris kosong tambahan misalnyaList<string> result = new List<string>(Regex.Split("fooo", @"(?<=\G.{4})", RegexOptions.Singleline));
fubo
9

Itu tidak cantik dan tidak cepat, tetapi bekerja, ini satu-liner dan itu 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();
Guffa
sumber
Apakah dijamin bahwa GroupBy mempertahankan urutan elemen?
Konstantin Spirin
ToCharArraytidak perlu karena stringitu IEnumerable<char>.
juharr
8

Baru-baru ini saya harus menulis sesuatu yang menyelesaikan ini di tempat kerja, jadi saya pikir saya akan memposting solusi saya untuk masalah ini. Sebagai bonus tambahan, fungsionalitas dari solusi ini menyediakan cara untuk membagi string ke arah yang berlawanan dan itu benar menangani karakter unicode seperti yang disebutkan sebelumnya oleh Marvin Pinto di atas. Jadi begini:

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

Juga, di sini adalah tautan gambar ke hasil menjalankan kode ini: http://i.imgur.com/16Iih.png

Michael Nelson
sumber
1
Saya melihat ada masalah dengan kode ini. Anda memiliki {str.ToString()}di akhir pernyataan IF pertama Anda. Apakah Anda yakin tidak bermaksud jahat str.String? Saya punya masalah dengan kode di atas, membuat perubahan itu, dan semuanya berfungsi.
gunr2171
@ gunr2171 Tampaknya jika str == null, baris itu juga akan memberikan NullReferenceException.
John Zabroski
5

Ini harus jauh lebih cepat dan lebih efisien daripada menggunakan LINQ atau pendekatan lain yang digunakan di sini.

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);
}
Jeff Mercado
sumber
Ini terlihat seperti pengecekan awal, tetapi tidak. Anda tidak mendapatkan kesalahan sampai Anda mulai menghitung enumerable. Anda perlu memecah fungsi Anda menjadi dua bagian, di mana bagian pertama melakukan pengecekan argumen, dan kemudian mengembalikan hasil bagian pribadi kedua yang melakukan enumerasi.
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;
}
HoloEd
sumber
4

Anda dapat menggunakan morelinq oleh Jon Skeet. Gunakan Batch seperti:

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

Ini akan mengembalikan 4 potongan untuk string "1111222233334444". Jika panjang string kurang dari atau sama dengan ukuran chunkBatch akan mengembalikan string sebagai satu-satunya elemenIEnumerable<string>

Untuk output:

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

dan itu akan memberi:

1111
2222
3333
4444
Habib
sumber
Di antara penulis MoreLINQ saya melihat Jonathan Skeet , tetapi tidak ada Jon Skeet . Jadi maksud Anda dengan Jon Skeet, atau apa? ;-)
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()));
}

dan pendekatan lain:

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));
    }
}
Chris Hayes
sumber
3

Enam tahun kemudian o_O

Hanya karena

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

atau

    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 semua kasus tepi ditangani.

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
Gerben Limburg
sumber
Bagaimana dengan kasus tepi "input adalah string kosong"? Saya berharap, sama seperti dengan Split, untuk mengembalikan IEnumerable dengan entri string kosong tunggal.
Nyerguds
3

Sederhana dan pendek:

// 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);
Tono Nam
sumber
Kenapa tidak digunakan .?
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;
}

Itu benar menangani panjang string input tidak dapat dibagi oleh chunkSize.

Harap dicatat bahwa kode tambahan mungkin diperlukan untuk menangani kasus tepi dengan anggun (null atau string input kosong, chunkSize == 0).

Troncho
sumber
2

Tip penting jika string yang sedang dipotong perlu untuk mendukung semua karakter Unicode.

Jika string ini untuk mendukung karakter internasional seperti 𠀋, maka pisahkan string menggunakan kelas System.Globalization.StringInfo. Menggunakan StringInfo, Anda dapat membagi string berdasarkan jumlah elemen teks.

string internationalString = '𠀋';

String di atas memiliki Panjang 2, karena String.Lengthproperti mengembalikan jumlah objek Char dalam contoh ini, bukan jumlah karakter Unicode.

Seth
sumber
2

Jawaban Terbaik, Termudah dan Umum :).

    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))));
    }
Sandeep Kushwah
sumber
Menghitung panjang pada baris terakhir adalah redundan, cukup gunakan Substringoverload yang tidak memerlukan parameter panjang originalString.Substring(i). Anda juga bisa menggunakannya >sebagai ganti >=cek Anda.
Racil Hilan
@RacilHilan Saya akan menguji perubahan kode dengan saran Anda dan memperbarui jawabannya. Saya senang seseorang dengan reputasi baik punya waktu untuk meninjau kode saya. :) Terima kasih, Sandeep
Sandeep Kushwah
2

Secara pribadi saya lebih suka solusi saya :-)

Ini menangani:

  • Panjang string yang merupakan kelipatan dari ukuran chunk.
  • Panjang string yang TIDAK merupakan kelipatan dari ukuran chunk.
  • Panjang string yang lebih kecil dari ukuran chunk.
  • NULL dan string kosong (melempar pengecualian).
  • Ukuran potongan lebih kecil dari 1 (melempar pengecualian).

Ini diimplementasikan sebagai metode ekstensi, dan menghitung jumlah potongan yang akan dihasilkan sebelumnya. Ini memeriksa potongan terakhir karena jika panjang teks tidak banyak itu perlu lebih pendek. Bersih, pendek, mudah dimengerti ... dan berfungsi!

    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;
    }
Ibai
sumber
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;
}
Gabriel
sumber
1
Saya suka jawaban ini banyak, tetapi mungkin Anda harus menggunakan if ((i +1) * chunk> = input.Length) daripada mencoba / menangkap karena pengecualian untuk kasus luar biasa.
nelsontruran
2

Saya pikir ini adalah jawaban langsung:

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

Dan itu mencakup kasus tepi.

Milad
sumber
2

Saya tahu pertanyaan sudah berumur bertahun-tahun, tetapi ini adalah implementasi Rx. Ini menangani length % chunkSize != 0masalah di luar kotak:

   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();
        }
Krzysztof Skowronek
sumber
1

Saya sedikit membangun solusi João. Apa yang saya lakukan secara berbeda adalah dalam metode saya, Anda benar-benar dapat menentukan apakah Anda ingin mengembalikan array dengan karakter yang tersisa atau apakah Anda ingin memotongnya jika karakter akhir tidak cocok dengan panjang chunk yang diperlukan, saya pikir itu cukup fleksibel dan kode cukup mudah:

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;
        }     
    }
}
Denys Wessels
sumber
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;
    }
Rikin Patel
sumber
Anda, eh, lupa parameter MaxLength.
Nyerguds
1

Berubah sedikit untuk mengembalikan bagian yang ukurannya tidak sama dengan 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;
    }
Abhishek Shrestha
sumber
Tidak yakin saya melihat penggunaan back-casting itu Listuntuk IEnumerable; semua yang dilakukan adalah menyembunyikan fungsi khusus Daftar yang mungkin ingin Anda gunakan. Tidak ada kerugian apa pun untuk hanya mengembalikan List.
Nyerguds
1

Saya tidak ingat siapa yang memberi saya ini, tetapi itu bekerja dengan baik. Saya mempercepat menguji sejumlah cara untuk memecah tipe Enumerable ke dalam kelompok. Penggunaannya akan seperti ini ...

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

Kode ekstensi akan terlihat seperti ini ...

#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
Jacob Raines
sumber
1
class StringHelper
{
    static void Main(string[] args)
    {
        string str = "Hi my name is vikas bansal and my email id is [email protected]";
        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;
    }
}
Vikas Bansal
sumber
Anda dapat sedikit meningkatkan kode Anda: menggeser ekspresi kenaikan i += offSetke forekspresi Anda .
JimiLoe
1

Modified (sekarang menerima setiap non nol stringdan setiap positif chunkSize) Konstantin Spirin 's solusi:

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

Tes:

  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));
Dmitry Bychenko
sumber
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

ren
sumber
yang satu ini menyimpan sisa string (post split) meskipun lebih pendek dari "chunkLenght", terima kasih
Jason Loki Smith
0

Berdasarkan jawaban poster lainnya, bersama dengan beberapa contoh penggunaan:

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();
}
Tom Gullen
sumber
0

Menggunakan ekstensi Buffer dari perpustakaan IX

    static IEnumerable<string> Split( this string str, int chunkSize )
    {
        return str.Buffer(chunkSize).Select(l => String.Concat(l));
    }
bradgonesurfing
sumber