Разделить строку на меньшие по длине - PullRequest
37 голосов
/ 09 июня 2010

Я хотел бы разбить строку на определенную переменную длины.
Требуется проверка границ, чтобы не взорваться, когда последняя часть строки не длиннее или не длиннее длины.Ищем самую краткую (но понятную) версию.

Пример:

string x = "AAABBBCC";
string[] arr = x.SplitByLength(3);
// arr[0] -> "AAA";
// arr[1] -> "BBB";
// arr[2] -> "CC"

Ответы [ 12 ]

62 голосов
/ 09 июня 2010

Вам нужно использовать цикл:

public static IEnumerable<string> SplitByLength(this string str, int maxLength) {
    for (int index = 0; index < str.Length; index += maxLength) {
        yield return str.Substring(index, Math.Min(maxLength, str.Length - index));
    }
}

Альтернатива:

public static IEnumerable<string> SplitByLength(this string str, int maxLength) {
    int index = 0;
    while(true) {
        if (index + maxLength >= str.Length) {
            yield return str.Substring(index);
            yield break;
        }
        yield return str.Substring(index, maxLength);
        index += maxLength;
    }
}

2 и альтернатива: (для тех, кто не может стоять while(true))

public static IEnumerable<string> SplitByLength(this string str, int maxLength) {
    int index = 0;
    while(index + maxLength < str.Length) {
        yield return str.Substring(index, maxLength);
        index += maxLength;
    }

    yield return str.Substring(index);
}
12 голосов
/ 09 июня 2010

Легкая для понимания версия:

string x = "AAABBBCC";
List<string> a = new List<string>();
for (int i = 0; i < x.Length; i += 3)
{
    if((i + 3) < x.Length)
        a.Add(x.Substring(i, 3));
    else
        a.Add(x.Substring(i));
}

Хотя желательно, чтобы 3 была хорошей константой.

6 голосов
/ 09 июня 2010

Это не очень лаконично, но я мог бы использовать такой метод расширения:

public static IEnumerable<string> SplitByLength(this string s, int length)
{
    for (int i = 0; i < s.Length; i += length)
    {
        if (i + length <= s.Length)
        {
            yield return s.Substring(i, length);
        }
        else
        {
            yield return s.Substring(i);
        }
    }
}

Обратите внимание, что я возвращаю IEnumerable<string>, а не массив. Если вы хотите преобразовать результат в массив, используйте ToArray:

string[] arr = x.SplitByLength(3).ToArray();
4 голосов
/ 09 июня 2010

Вот что я бы сделал:

public static IEnumerable<string> EnumerateByLength(this string text, int length) {
    int index = 0;
    while (index < text.Length) {
        int charCount = Math.Min(length, text.Length - index);
        yield return text.Substring(index, charCount);
        index += length;
    }
}

Этот метод обеспечит отложенное выполнение (что на самом деле не имеет значения для такого неизменяемого класса, как string, но стоит отметить).

Тогда, если вы хотите, чтобы метод заполнял массив для вас, вы могли бы иметь:

public static string[] SplitByLength(this string text, int length) {
    return text.EnumerateByLength(length).ToArray();
}

Причина, по которой я бы выбрал имя EnumerateByLength вместо SplitByLength для "ядра"Метод заключается в том, что string.Split возвращает string[], поэтому, на мой взгляд, есть приоритет для методов, имена которых начинаются с Split для возврата массивов.

Хотя это только я.

3 голосов
/ 17 января 2011

Мое решение:

public static string[] SplitToChunks(this string source, int maxLength)
{
    return source
        .Where((x, i) => i % maxLength == 0)
        .Select(
            (x, i) => new string(source
                .Skip(i * maxLength)
                .Take(maxLength)
                .ToArray()))
        .ToArray();
}

Я на самом деле скорее использую List<string> вместо string[].

1 голос
/ 10 июня 2010

Еще один небольшой вариант (классический, но простой и прагматичный):

class Program
{
    static void Main(string[] args) {
        string msg = "AAABBBCC";

        string[] test = msg.SplitByLength(3);            
    }
}

public static class SplitStringByLength
{
    public static string[] SplitByLength(this string inputString, int segmentSize) {
        List<string> segments = new List<string>();

        int wholeSegmentCount = inputString.Length / segmentSize;

        int i;
        for (i = 0; i < wholeSegmentCount; i++) {
            segments.Add(inputString.Substring(i * segmentSize, segmentSize));
        }

        if (inputString.Length % segmentSize != 0) {
            segments.Add(inputString.Substring(i * segmentSize, inputString.Length - i * segmentSize));
        }

        return segments.ToArray();
    }
}
1 голос
/ 10 июня 2010

UPD: использование некоторого Linq для краткости


static IEnumerable EnumerateByLength(string str, int len)
        {
            Match m = (new Regex(string.Format("^(.{{1,{0}}})*$", len))).Match(str);
            if (m.Groups.Count &lt= 1)
                return Empty;
            return (from Capture c in m.Groups[1].Captures select c.Value);
        }

Начальная версия:


        static string[] Empty = new string [] {};

        static string[] SplitByLength(string str, int len)
        {
            Regex r = new Regex(string.Format("^(.{{1,{0}}})*$",len));
            Match m = r.Match(str);
            if(m.Groups.Count &lt= 1)
                return Empty;

            string [] result = new string[m.Groups[1].Captures.Count];
            int ix = 0;
            foreach(Capture c in m.Groups[1].Captures)
            {
                result[ix++] = c.Value;
            }
            return result;
        }
1 голос
/ 09 июня 2010

Использование Batch из MoreLinq, в .Net 4.0:

public static IEnumerable<string> SplitByLength(this string str, int length)
{
    return str.Batch(length, String.Concat);
}

На 3.5 Concat нужен массив, поэтому мы можем использовать Concat с ToArray или new String:

public static IEnumerable<string> SplitByLength(this string str, int length)
{
    return str.Batch(length, chars => new String(chars.ToArray()));
}

Может быть немного неинтуитивно смотреть на строку как на коллекцию символов, поэтому может быть предложено манипулирование строкой.

0 голосов
/ 15 марта 2019

У меня есть рекурсивное решение:

    public List<string> SplitArray(string item, int size)
    {
        if (item.Length <= size) return new List<string> { item };
        var temp = new List<string> { item.Substring(0,size) };
        temp.AddRange(SplitArray(item.Substring(size), size));
        return temp;
    }

Thoug, он не возвращает IEnumerable, а список

0 голосов
/ 11 марта 2014

У меня был странный сценарий, когда я сегментировал строку, затем переставлял сегменты (то есть переворачивал), прежде чем объединять их, а затем мне нужно было обратить сегментацию в обратном направлении. Вот обновление принятого ответа от @ SLaks :

    /// <summary>
    /// Split the given string into equally-sized segments (possibly with a 'remainder' if uneven division).  Optionally return the 'remainder' first.
    /// </summary>
    /// <param name="str">source string</param>
    /// <param name="maxLength">size of each segment (except the remainder, which will be less)</param>
    /// <param name="remainderFirst">if dividing <paramref name="str"/> into segments would result in a chunk smaller than <paramref name="maxLength"/> left at the end, instead take it from the beginning</param>
    /// <returns>list of segments within <paramref name="str"/></returns>
    /// <remarks>Original method at /2452051/razdelit-stroku-na-menshie-po-dline </remarks>
    private static IEnumerable<string> ToSegments(string str, int maxLength, bool remainderFirst = false) {
        // note: `maxLength == 0` would not only not make sense, but would result in an infinite loop
        if(maxLength < 1) throw new ArgumentOutOfRangeException("maxLength", maxLength, "Should be greater than 0");
        // correct for the infinite loop caused by a nonsensical request of `remainderFirst == true` and no remainder (`maxLength==1` or even division)
        if( remainderFirst && str.Length % maxLength == 0 ) remainderFirst = false;

        var index = 0;
        // note that we want to stop BEFORE we reach the end
        // because if it's exact we'll end up with an
        // empty segment
        while (index + maxLength < str.Length)
        {
            // do we want the 'final chunk' first or at the end?
            if( remainderFirst && index == 0 ) {
                // figure out remainder size
                var remainder = str.Length % maxLength;
                yield return str.Substring(index, remainder);

                index += remainder;
            }
            // normal stepthrough
            else {
                yield return str.Substring(index, maxLength);
                index += maxLength;
            }
        }

        yield return str.Substring(index);
    }//---  fn  ToSegments

(я также исправил ошибку в оригинальной версии while, в результате которой пустой сегмент, если maxLength==1)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...