Розщеплення струни на шматки певного розміру


218

Припустимо, у мене був рядок:

string str = "1111222233334444"; 

Як я можу розбити цю струну на шматки певного розміру?

наприклад, розбивши це на розміри 4, повернеться рядки:

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

18
Навіщо використовувати LINQ або регулярні вирази, коли стандартні функції обробки рядків C # можуть це робити з меншими зусиллями та більшою швидкістю? Також, що станеться, якщо рядок має непарну кількість символів у довжину?
Ян Кемп

7
"Я хотів би уникати циклів" - чому?
Мітч Пшеничний

12
Використання простого циклу - це, безумовно, те, що дає найкращі показники.
Гуффа

4
nichesoftware.co.nz/blog/200909/linq-vs-loop-performance - це досить гарне порівняння між linq та фактичним циклом за допомогою масиву. Я сумніваюсь, що ви коли-небудь знайдете linkq швидше, ніж написаний вручну код, тому що він постійно викликує делегатів під час запуску, які важко оптимізувати. Хоча Linq - це веселіше :)
Сліпий

2
Незалежно від того, використовуєте ви LINQ або регулярні вирази, цикл все ще є.
Антон Тихий

Відповіді:


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

Зверніть увагу, що для витонченого оброблення крайових справ ( nullабо порожнього вхідного рядка, chunkSize == 0довжини вхідного рядка не можна ділити на chunkSizeтощо) може знадобитися додатковий код . Оригінальне запитання не визначає жодних вимог до цих крайових випадків, і в реальному житті вимоги можуть відрізнятися, тому вони не входять у рамки цієї відповіді.


3
@Harry Хороший улов! Це можна виправити за допомогою випадаючого потрійного виразу на параметр count підрядок. Що - щось на кшталт: (i * chunkSize + chunkSize <= str.Length) ? chunkSize : str.Length - i * chunkSize. Додатковою проблемою є те, що ця функція не враховує нуль str. Це може бути виправлено шляхом обертати все про повернутий в іншому потрійному вираженні: (str != null) ? ... : Enumerable.Empty<String>();.
Drew Spickes

7
Це було близько, але в відміну від попередніх 30 upvoters, я повинен був змінити обмеження на кількість петель в діапазоні від str.Length / chunkSizeдоdouble length = str.Length; double size = chunkSize; int count = (int)Math.Ceiling(length/size); return Enumerable.Range(0, count)...
щілини

4
@KonstantinSpirin Я згоден, якщо код спрацював. Він обробляє лише той випадок, коли рядок є кратним chunkSize, решта рядка втрачається. Будь ласка, поправте. Також майте на увазі, що LINQ і його магія не так просто зрозуміти комусь, хто просто хоче шукати рішення цієї проблеми. Тепер людина повинна зрозуміти, що виконують функції Enumerable.Range () та .Select (). Я не заперечую, що ви повинні мати розуміння цього, щоб написати C # /. NET-код, оскільки ці функції були в BCL вже багато років.
CodeMonkeyKing

6
Старшина теми сказав у коментарях, що StringLength % 4 will always be 0. Якщо Linqце не так просто зрозуміти, то є й інші відповіді, які використовують петлі та результати. Кожен може вільно вибрати рішення, яке їй найбільше подобається. Ви можете опублікувати свій код як відповідь, і люди з радістю проголосують за нього.
Костянтин Спірін

3
Enumerable.Range (0, (str.length + chunkSize - 1) / chunkSize) .Select (я => str.Substring (I * chunkSize, Math.min (str.length - я * chunkSize, chunkSize)))
Стен Петров

135

У поєднанні голуба + відповіді Констатіна ...

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

Це буде працювати для всіх рядків, які можна розділити на цілу кількість фрагментів, і в іншому випадку викине виняток.

Якщо ви хочете підтримувати рядки будь-якої довжини, ви можете використовувати наступний код:

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

Однак ОП прямо заявив, що він цього не потребує; дещо довше і важче читати, трохи повільніше. У дусі KISS та YAGNI я б пішов із першим варіантом: це, мабуть, найефективніша реалізація, яка є дуже короткою, читабельною і, що важливо, є винятком для невідповідних даних.


4
+1 варто кимнути. якось вдаряє цвях по голові. він шукає стислий ситнакс, і ти також даєш (ймовірно) кращі показники.
голуб

7
І якщо ви зробите це "статичним ... Chunk (ця рядок str, int chunkSize) {" у вас навіть є ще один "новий" C # -Файтер. Тоді ви можете написати "1111222233334444" .Chunk (4).
MartinStettner

1
@MartinStettner: Це, звичайно, гідна ідея, якщо це звичайна операція.
Еймон Нербонна

Вам слід включити лише останній код. Перший вимагає, щоб ви зрозуміли і перевірили, чи є рядок кратним розміром фрагмента перед використанням, або зрозуміти, що він не поверне решту рядка.
CodeMonkeyKing

Питання ОП не дає зрозуміти, чи потрібна йому ця функціональність. Перше рішення простіше, швидше і надійніше не вдається за винятком, якщо рядок не можна рівномірно розділити на вказаний розмір шматка. Я погоджуюсь, що повернення «неправильних» результатів було б поганим, але це не те, що це робить - це просто кидає виняток, тому я б з цим користувався, якщо ви можете жити з обмеженням.
Еймон Нербонна

56

Чому б не петлі? Ось щось, що могло б зробити це досить добре:

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

Я не знаю, як би ви мали справу з випадком, коли рядок не є коефіцієнтом 4, але не сказати, що ідея не можлива, просто цікаво мотивація цього, якщо простий цикл робить це дуже добре? Очевидно, що вищезгадане можна очистити і навіть застосувати як метод розширення.

Або як згадується в коментарях, ви знаєте, що це / 4 тоді

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

1
Можна витягнути int chunkSize = 4зовні петлю. Він буде модифікований лише після остаточного пропуску.
Джон Фемінелла

+1 для простого та ефективного рішення - саме так я би зробив це, хоча б і використовував i += chunkSizeзамість цього.
Ян Кемп

Напевно, незначне посвідчення, але ви, ймовірно, також повинні витягнути str.Lengthз циклу та перейти в локальну змінну. Оптимізатор C # може бути в змозі вбудувати довжину масиву, але я думаю, що код, як написано, буде робити виклик методу для кожного циклу, що не ефективно, оскільки розмір strніколи не змінюється.
Даніель Приден

@Daniel, вклади свою ідею туди. хоча я не впевнений, що це не буде обчислено під час виконання, але це інше питання;)
голуб

@Daniel, повертаючись до цього, майже впевнений, що компілятор отримає цю оптимізацію.
голуб

41

Використання регулярних виразів та Linq :

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

Я вважаю це більш читабельним, але це лише особиста думка. Це також може бути однолінійний:).


7
Змініть шаблон на @ "\ d {1,4}", і він працює для будь-якої довжини рядка. :)
Guffa

3
+1 Хоча це повільніше, ніж інші рішення, це, безумовно, легко читається. Мені не ясно, чи потрібні ОП цифри чи довільні символи; напевно, було б розумно замінити \dклас символів на а .та вказати RegexOptions.Singleline.
Еймон Нербонна

2
або просто Regex.Matches (s, @ "\ d {1,4}"). Виберіть (m => m.Value) .ToList (); Я ніколи не розумів цього альтернативного синтаксису, який служить лише для придушення того, що ми використовуємо методи розширення.
Даг

38

Це засновано на рішенні @dove але реалізовано як метод розширення.

Переваги:

  • Спосіб розширення
  • Кришки кутових шаф
  • Розділяє рядок з будь-якими знаками: цифрами, літерами та іншими символами

Код

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

Використання

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

Одиничні випробування вилучені для стислості (див. Попередню редакцію )


Цікаве рішення, але заради уникнення перевірок, що перевищують нулі на вході, здається більш логічним дозволити порожній рядку просто повернути одну частину порожнього рядка:if (str.Length == 0) yield return String.Empty; else { for... }
Nyerguds

Я маю на увазі, так нормальний String.Split обробляє порожні рядки; він повертає один запис порожнього рядка.
Nyerguds

Бічна примітка: ваш приклад використання неправильний. Ви не можете просто IEnumerableнадати масив, особливо не неявно.
Nyerguds

Мені особисто подобається називати цей метод Chunkify.. Це не моє, я не пам'ятаю, де я бачила це ім'я, але мені це було дуже приємно
quetzalcoatl

20

Як це для однолінійного?

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

З цим регулярним виразом не має значення, чи є останній фрагмент менше чотирьох символів, оскільки він лише коли-небудь дивиться на символів, що знаходяться за ним.

Я впевнений, що це не найефективніше рішення, але мені довелося просто викинути його туди.


у випадку target.Lenght % ChunckSize == 0цього повертається додатковий порожній рядок, наприкладList<string> result = new List<string>(Regex.Split("fooo", @"(?<=\G.{4})", RegexOptions.Singleline));
fubo

9

Це не красиво і не швидко, але це працює, це однолінійний і це 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();

Чи гарантується, що GroupBy зберігає порядок елементів?
Костянтин Спірін

ToCharArrayє непотрібним, оскільки stringє IEnumerable<char>.
juharr

8

Нещодавно мені довелося написати щось, що досягає цього на роботі, тому я подумав, що опублікую своє рішення цієї проблеми. Як додатковий бонус, функціональність цього рішення надає спосіб розділити рядок у зворотному напрямку, і він правильно обробляє символи unicode, як раніше згадував Марвін Пінто вище. Отже, ось це:

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

Також ось посилання на зображення на результати запуску цього коду: http://i.imgur.com/16Iih.png


1
Я помітив проблему з цим кодом. Ви маєте {str.ToString()}в кінці своєї першої заяви IF. Ви впевнені, що не мали на увазі str.String? У мене була проблема з кодом вище, внесла ці зміни, і все спрацювало.
gunr2171

@ gunr2171 Схоже, що якщо str == null, цей рядок також дасть NullReferenceException.
Іван Заброський

5

Це має бути набагато швидше та ефективніше, ніж використання LINQ або інших підходів, що використовуються тут.

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

Це виглядає , як він робить ранню перевірку, але це не робить. Ви не отримаєте помилку, поки не почнете перераховувати нумерацію. Вам потрібно розбити свою функцію на дві частини, де перша частина виконує перевірку аргументів, а потім повертає результати другої, приватної частини, яка робить перерахування.
ЕрікЕ

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

Ви можете використовувати morelinq від Джона Скіта. Використовуйте пакет :

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

Це поверне 4 шматки для рядка "1111222233334444". Якщо довжина рядка менше або дорівнює розміру відрізка Batch, поверне рядок як єдиний елементIEnumerable<string>

Для виводу:

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

і це дасть:

1111
2222
3333
4444

Серед авторів MoreLINQ я бачу Джонатана Скіта , але немає Джона Скіта . То ти мав на увазі Джон Скіта, чи що? ;-)
Snađошƒаӽ

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

та інший підхід:

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

Через шість років o_O

Лише тому що

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

або

    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 обробляються всі крайові корпуси.

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

А як же з крайовим регістром "вхід - порожній рядок"? Я очікував, що, як і у розділі Split, поверне IEnumerable з єдиним порожнім рядком, що містить запис.
Nyerguds

3

Простий і короткий:

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

Чому б не використовувати .?
марш

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

Він правильно обробляє довжину вхідного рядка, не поділяється на chunkSize.

Зауважте, що для витонченого оброблення крайових справ може знадобитися додатковий код (нульовий або порожній рядок вводу, chunkSize == 0).


2

Важлива порада, якщо в рядку, що вводиться, потрібно підтримувати всі символи Unicode.

Якщо рядок повинен підтримувати міжнародні символи, наприклад 𠀋, розділіть рядок за допомогою класу System.Globalization.StringInfo. Використовуючи StringInfo, ви можете розділити рядок на основі кількості текстових елементів.

string internationalString = '𠀋';

Наведений рядок має довжину 2, оскільки String.Lengthвластивість повертає в цьому випадку кількість об'єктів Char, а не кількість символів Unicode.


2

Найкращий, найпростіший і загальний відповідь :).

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

Обчислення довжини в останньому рядку є зайвим, просто використовуйте Substringперевантаження, яка не вимагає параметра довжини originalString.Substring(i). Також ви можете використовувати >замість >=чека.
Расіл Хілан

@RacilHilan Я перевірю зміни коду з вашою пропозицією та оновлю відповідь. Я радий, що хтось із такою доброю репутацією отримав час переглянути мій код. :) Дякую, Sandeep
Sandeep Kushwah

2

Особисто я віддаю перевагу своєму рішенню :-)

Він обробляє:

  • Довжина струн, яка кратна розміру шматка.
  • Довжина рядків, яка НЕ ​​кратна розміру шматка.
  • Довжина струн, менша за розмір шматка.
  • NULL та порожні рядки (кидає виняток).
  • Частини розміру менше 1 (кидає виняток).

Він реалізований як метод розширення, і він обчислює кількість шматок, які збираються заздалегідь. Він перевіряє останній фрагмент, тому що, якщо довжина тексту не є кратною, вона повинна бути коротшою. Чисто, коротко, легко зрозуміти ... і працює!

    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
Мені ця відповідь дуже подобається, але, можливо, вам слід скористатися, якщо ((i + 1) * chunk> = input.Length) замість спробувати / catch як винятки є для виняткових випадків.
nelsontruran

2

Я думаю, що це пряма відповідь:

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

І він охоплює крайові корпуси.


2

Я знаю, що питання років, але ось реалізація Rx. Він вирішує length % chunkSize != 0проблему поза коробкою:

   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

Я трохи допоміг над рішенням Жоао. Що я зробив по-іншому, в моєму методі ви можете фактично вказати, чи хочете ви повернути масив із залишковими символами, чи ви хочете усікати їх, якщо кінцеві символи не відповідають необхідній довжині, я думаю, що це досить гнучко і код досить прямо вперед:

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

Ви, е, забули параметр MaxLength.
Nyerguds

1

Трохи змінено, щоб повернути деталі, розмір яких не дорівнює розміру

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

Не впевнений, я бачу використання зворотного кастингу Listдля цього IEnumerable; все, що потрібно, це приховувати функції, що стосуються списку, які ви можете використовувати. Немає жодних недоліків у тому, щоб просто повернути своє List.
Nyerguds

1

Не можу згадати, хто мені це дав, але це чудово працює. Я швидко перевірив декілька способів розбити численні типи на групи. Використання було б саме таким ...

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

Код розширення буде виглядати приблизно так ...

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

Ви можете трохи покращити свій код: перенесіть приріст i += offSetна свій forвираз.
JimiLoe

1

Змінено (тепер воно приймає будь-яке ненуле stringта будь-яке позитивне chunkSize) рішення Костянтина Спіріна :

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

Тести:

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

демонстрація


цей зберігає залишок рядка (пост-розділення), навіть він коротший, ніж "chunkLenght", дякую
Джейсон Локі Сміт

0

На основі відповідей інших плакатів, а також деяких зразків використання:

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.