20

我知道 String.Split 的概念之前已经通过多种不同的方法得到解决,但我对这个问题的 LINQ 解决方案特别感兴趣。

我试图编写一个扩展类来处理拆分,但是这两种尝试都有一些主要问题。因此,对于以下内容:

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 整除的子字符串,但 Select 语句中的“i”值与 Where 语句中的“i”值不对应,因此我得到如下结果:{“ 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();
}

感谢所有优秀的建议。

4

8 回答 8

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

You can also use morelinq's batch

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

If you don't mind using side effects, this is possible too

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

And Of course every string operation question deserves a Regex answer :)

var res3 = Regex.Split(s, @"(?<=\G.{4})");
于 2013-08-08T23:17:35.977 回答
11

Here is another solution:

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));
于 2013-08-09T04:31:56.770 回答
10

您可以使用此扩展方法,该方法通过简单的子字符串获取实现(我相信它比枚举字符并将它们加入字符串更快):

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);
}
于 2013-08-08T23:42:43.130 回答
6
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)

您当然可以附加 a.ToList()以返回 List 而不是IEnumerable.

于 2013-08-08T23:42:11.443 回答
4

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))。

于 2013-08-08T23:33:27.927 回答
3

这似乎有效:

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;
    }
}
于 2013-08-08T23:23:33.280 回答
1

这里有几种 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) ;
  }

}
于 2013-08-08T23:28:31.313 回答
1

这也有效,但需要“解包” 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)
  );
}

编辑:使用 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因为它鼓励有状态的代码(在父或全局范围内的计数器、索引等)。

于 2014-04-17T16:18:04.490 回答