Подтвердить что ты не робот

Разделить строку в список строк длиной N, используя LINQ

Я знаю, что понятие String.Split было рассмотрено ранее с помощью множества различных подходов, но меня особенно интересует решение LINQ для этого вопроса.

Я попытался написать класс расширения для обработки split, но обе попытки имеют некоторые серьезные проблемы. Итак, для следующего:

string s = "ABCDEFGHIJKLMNOPQRSTUVWX";
var results = s.SplitEvery(4);

Мне нужен список вроде: { "ABCD", "EFGH", "IJKL", "MNOP", "QRST", "UVWX" }

Вот мой класс расширения:

public static class Extensions
{
    public static List<string> SplitEvery(this string s, int n)
    {
        List<string> list = new List<string>();

        var Attempt1 = s.Select((c, i) => i % n== 0 ? s.Substring(i, n) : "|").Where(x => x != "|").ToList();

        var Attempt2 = s.Where((c, i) => i % n== 0).Select((c, i) => s.Substring(i, n)).ToList();

        return list;
    }
}

Попытка 1 вставляет фиктивную строку "|" каждый раз, когда условие не выполняется, затем удаляет все экземпляры фиктивной строки для создания окончательного списка. Он работает, но создание плохих строк кажется ненужным дополнительным шагом. Кроме того, эта попытка не выполняется, если строка не делится на n.

Попытка 2 заключалась в том, что я пытался выбрать только подстроки, где индекс был делимым на N, но значение "i" в выражении Select не соответствует значению "i" в инструкции Where, поэтому я получаю результаты как: { "ABCD", "BCDE" и т.д.}}

Мне кажется, что я близок к хорошему решению, но могу использовать полезный толчок в правильном направлении. Любые предложения?

[изменить]

В итоге у меня появилось сочетание предложений для обработки моего разделителя строк. Это может быть не самый быстрый, но, как новичок в LINQ, эта реализация была наиболее кратким и понятным для меня.

public static List<string> SplitEvery(this string s, int size)
{
    return s.Select((x, i) => i)
        .Where(i => i % size == 0)
        .Select(i => String.Concat(s.Skip(i).Take(size))).ToList();
}

Спасибо за все прекрасные предложения.

4b9b3361

Ответ 1

Вот еще одно решение:

var result = s.Select((x, i) => i)
              .Where(i => i % 4 == 0)
              .Select(i => s.Substring(i, s.Length - i >= 4 ? 4 : s.Length - i));

Ответ 2

string s = "ABCDEFGHIJKLMNOPQRSTUVWX";
var results = s.Select((c, i) => new { c, i })
            .GroupBy(x => x.i / 4)
            .Select(g => String.Join("",g.Select(y=>y.c)))
            .ToList();

Вы также можете использовать morelinq batch

var res = s.Batch(4).Select(x => String.Join("", x)).ToList();

Если вы не против использования побочных эффектов, это тоже возможно.

var res2 = s.SplitEvery(4).ToList();

public static IEnumerable<string> SplitEvery(this string s, int n)
{
    int index = 0;
    return s.GroupBy(_=> index++/n).Select(g => new string(g.ToArray()));
}

И, конечно, каждый вопрос о строковой операции заслуживает ответа Regex:)

var res3 = Regex.Split(s, @"(?<=\G.{4})");

Ответ 3

Вы можете использовать этот метод расширения, который реализуется с помощью простой подстроки (я считаю, что это быстрее, чем перечисление над символами и объединение их в строки):

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

    if (index < s.Length)
        yield return s.Substring(index, s.Length - index);
}

Ответ 4

public static IEnumerable<string> SplitEvery(this string s, int length)
{
    return s.Where((c, index) => index % length == 0)
           .Select((c, index) => String.Concat(
                s.Skip(index * length).Take(length)
             )
           );
}

Жюри не знает, будет ли new String(chars.ToArray()) быстрее или медленнее для этого, чем String.Concat(chars).

Вы можете, конечно, добавить .ToList(), чтобы вернуть список, а не IEnumerable.

Ответ 5

Substring должно быть в порядке, чтобы выбрать 4-символьные части строки. Вам просто нужно быть осторожным с последней порцией:

new Func<string, int, IEnumerable<string>>(
        (string s, int n) => 
           Enumerable.Range(0, (s.Length + n-1)/n)
           .Select(i => s.Substring(i*n, Math.Min(n, s.Length - i*n)))) 
("ABCDEFGHIJKLMNOPQRSTUVWX", 4)

Примечание: если этот ответ будет преобразован в операцию в универсальном перечислимом, ему придется многократно повторять сбор (Count() и Substring, преобразованный в Skip(i*n).Take(n)).

Ответ 6

Это работает:

public static IEnumerable<string> SplitEvery(this string s, int n) {
    var enumerators = Enumerable.Repeat(s.GetEnumerator(), n);
    while (true) {
        var chunk = string.Concat(enumerators
            .Where(e => e.MoveNext())
            .Select(e => e.Current));
        if (chunk == "") yield break;
        yield return chunk;
    }
}

Ответ 7

Вот несколько способов LINQy:

public static IEnumerable<string> SplitEvery( this IEnumerable<char> s , int n )
{
  StringBuilder sb = new StringBuilder(n) ;
  foreach ( char c in s )
  {
    if ( sb.Length == n )
    {
      yield return sb.ToString() ;
      sb.Length = 0 ;
    }
    sb.Append(c) ;
  }
}

или

public static IEnumerable<string> SplitEvery( this string s , int n )
{
  int limit = s.Length - ( s.Length % n ) ;
  int i = 0 ;

  while ( i < limit )
  {
    yield return s.Substring(i,n) ;
    i+=n ;
  }

  if ( i < s.Length )
  {
    yield return s.Substring(i) ;
  }

}

Ответ 8

Это также работает, но требует "разворачивания" IGrouping<x,y>:

public static IEnumerable<String> Split(this String me,int SIZE) {
  //Works by mapping the character index to a 'modulo Staircase'
  //and then grouping by that 'stair step' value
  return me.Select((c, i) => new {
    step = i - i % SIZE,
    letter = c.ToString()
  })
  .GroupBy(kvp => kvp.step)
  .Select(grouping => grouping
    .Select(g => g.letter)
    .Aggregate((a, b) => a + b)
  );
}

EDIT: используя ленивые механизмы оценки LINQ (yield return), вы также можете добиться этого, используя рекурсию

public static IEnumerable<String> Split(this String me, int SIZE) {      
  if (me.Length > SIZE) {
    var head = me.Substring(0,SIZE);
    var tail = me.Substring(SIZE,me.Length-SIZE);
    yield return head;        
    foreach (var item in tail.Split(SIZE)) {
      yield return item; 
    }
  } else { 
    yield return me;
  }
}

Хотя, лично, я держусь подальше от Substring, потому что он поощряет полноценный код (счетчики, индексы и т.д. в родительских или глобальных областях).