【问题标题】:C#: Cleanest way to divide a string array into N instances N items longC#:将字符串数组划分为 N 个实例、N 个项目长的最简洁方法
【发布时间】:2010-11-03 18:33:31
【问题描述】:

我知道如何以一种丑陋的方式做到这一点,但我想知道是否有更优雅和简洁的方法。

我有一个电子邮件地址的字符串数组。假设字符串数组是任意长度的——它可能有几个项目,也可能有很多项目。我想构建另一个由字符串数组中的 50 个电子邮件地址组成的字符串,直到数组末尾,并在每 50 个电子邮件地址之后调用发送操作,在 Send() 方法中使用 50 个地址的字符串。

更普遍的问题是做这种事情的最干净/最清晰的方法是什么。我有一个解决方案,它是我学习 VBScript 的遗产,但我敢打赌 C# 中有更好的方法。

【问题讨论】:

  • 感谢大家的精彩回答——确实,您的方法比我过去实施的简单循环解决方案要好得多。这正是我希望得到的那种指导!

标签: c# arrays string loops


【解决方案1】:

我认为我们需要对这份清单的具体内容有更多的了解,才能给出明确的答案。现在我假设它是一个以分号分隔的电子邮件地址列表。如果是这样,您可以执行以下操作来获取分块列表。

public IEnumerable<string> DivideEmailList(string list) {
  var last = 0;
  var cur = list.IndexOf(';');
  while ( cur >= 0 ) {
    yield return list.SubString(last, cur-last);
    last = cur + 1;
    cur = list.IndexOf(';', last);
  }
}

public IEnumerable<List<string>> ChunkEmails(string list) {
  using ( var e = DivideEmailList(list).GetEnumerator() ) {
     var list = new List<string>();
     while ( e.MoveNext() ) {
       list.Add(e.Current);
       if ( list.Count == 50 ) {
         yield return list;
         list = new List<string>();
       }
     }
     if ( list.Count != 0 ) {
       yield return list;
     }
  }
}

【讨论】:

  • 其实他说他有一个email地址数组,所以不用分号分割。
  • 并且输出应该是一个组合字符串。
【解决方案2】:

我将遍历数组并使用 StringBuilder 创建列表(我假设它由 ; 分隔,就像您使用电子邮件一样)。只需在您达到 mod 50 或结束时发送。

void Foo(string[] addresses)
{
    StringBuilder sb = new StringBuilder();

    for (int i = 0; i < addresses.Length; i++)
    {
        sb.Append(addresses[i]);
        if ((i + 1) % 50 == 0 || i == addresses.Length - 1)
        {
            Send(sb.ToString());
            sb = new StringBuilder();
        }
        else
        {
            sb.Append("; ");
        }
    }
}

void Send(string addresses)
{
}

【讨论】:

    【解决方案3】:

    似乎类似于这个问题:Split a collection into n parts with LINQ?

    Hasan Khan 的答案的修改版本应该可以解决问题:

    public static IEnumerable<IEnumerable<T>> Chunk<T>(
        this IEnumerable<T> list, int chunkSize)
    {
        int i = 0;
        var chunks = from name in list
                     group name by i++ / chunkSize into part
                     select part.AsEnumerable();
        return chunks;
    }
    

    使用示例:

    var addresses = new[] { "a@example.com", "b@example.org", ...... };
    
    foreach (var chunk in Chunk(addresses, 50))
    {
        SendEmail(chunk.ToArray(), "Buy V14gr4");
    }
    

    【讨论】:

    • 对我来说似乎有点不必要!仍然需要调用 string.Join 方法,并且可以直接寻址数组中的子范围。
    • 很好,但我们可以在不改变“i”的情况下做到这一点。看我的回答。
    • 也许我遗漏了一些东西,但 Chunk 不是扩展方法,所以你不应该使用addresses.Chunk 吗?
    【解决方案4】:

    听起来输入由一个大数组中的单独电子邮件地址字符串组成,而不是一个字符串中的多个电子邮件地址,对吧?并且在输出中,每个批次都是一个单独的组合字符串。

    string[] allAddresses = GetLongArrayOfAddresses();
    
    const int batchSize = 50;
    
    for (int n = 0; n < allAddresses.Length; n += batchSize)
    {
        string batch = string.Join(";", allAddresses, n, 
                          Math.Min(batchSize, allAddresses.Length - n));
    
        // use batch somehow
    }
    

    【讨论】:

      【解决方案5】:

      假设您使用的是 .NET 3.5 和 C# 3,这样的东西应该可以很好地工作:

      string[] s = new string[] {"1", "2", "3", "4"....};
      
      for (int i = 0; i < s.Count(); i = i + 50)
      {
          string s = string.Join(";", s.Skip(i).Take(50).ToArray());
          DoSomething(s);
      }
      

      【讨论】:

      • 跳过/取走的东西是不必要的复制 - 有一个重载的 string.Join 可以让你在数组中指定一个范围。
      • 但是,如果您指定的项目多于数组中的项目,Take 不会引发异常。您的 Math.Min 解决了这个问题,但我发现 linq 解决方案更具可读性和更易于理解。
      • 这里有一个错误。循环的第一次迭代你将跳过 0,并取 50,在第二次迭代中你将跳过 2500(我变成 50,所以 i*50 = 2500),而不是 50
      【解决方案6】:

      你要优雅简洁,我给你优雅简洁:

      var fifties = from index in Enumerable.Range(0, addresses.Length) 
                    group addresses[index] by index/50;
      foreach(var fifty in fifties)
          Send(string.Join(";", fifty.ToArray());
      

      当你不需要的时候,为什么要搞乱那些糟糕的循环代码呢?您想按 50 组分组,然后按 50 组分组。 这就是组运算符的用途!

      更新:评论者 MoreCoffee 询问这是如何工作的。假设我们想三人一组,因为这样更容易输入。

      var threes = from index in Enumerable.Range(0, addresses.Length) 
                    group addresses[index] by index/3;
      

      假设有九个地址,索引从零到八

      这个查询是什么意思?

      Enumerable.Range 是从零开始的九个数字的范围,所以0, 1, 2, 3, 4, 5, 6, 7, 8。

      范围变量index 依次采用这些值。

      然后我们检查每个对应的addresses[index] 并将其分配给一个组。

      我们将它分配到哪个组?到群组index/3。在 C# 中整数算术向零舍入,因此索引 0、1 和 2 除以 3 时变为 0。索引 3、4、5 除以 3 时变为 1。索引 6、7、8 变为 2。

      因此我们将addresses[0]、addresses[1] 和addresses[2] 分配给第0 组,addresses[3]、addresses[4] 和addresses[5] 分配给第1 组,依此类推。

      查询的结果是三组的序列,每组是三项的序列。

      这有意义吗?

      还请记住,查询表达式的结果是一个代表此操作的查询。在foreach 循环执行之前,它不会执行操作。

      【讨论】:

      • 除了糟糕的循环代码之外,是否有同样优雅的批处理 IEnumerable 解决方案(即当您不知道大小时)?
      • 当然。看看 DTB 的回答。
      • by index/50 是如何分组的?我知道它有效,但我对合成器有点困惑
      • @MoreCoffee:我添加了一些解释性文字。
      • 确实非常干净 :) 基于您的回答的通用扩展方法 - here。
      【解决方案7】:

      我觉得这样既简单又快。下面的例子把长句分成15个部分,但是你可以传递batch size作为参数让它动态化。这里我简单地用“/n”来划分。

       private static string Concatenated(string longsentence)
       {
           const int batchSize = 15;
           string concatanated = "";
           int chanks = longsentence.Length / batchSize;
           int currentIndex = 0;
           while (chanks > 0)
           {
               var sub = longsentence.Substring(currentIndex, batchSize);
               concatanated += sub + "/n";
               chanks -= 1;
               currentIndex += batchSize;
           }
           if (currentIndex < longsentence.Length)
           {
               int start = currentIndex;
               var finalsub = longsentence.Substring(start);
               concatanated += finalsub;
           }
           return concatanated;
       }
      

      这里显示拆分操作的结果。

       var parts = Concatenated(longsentence).Split(new string[] { "/n" }, StringSplitOptions.None);
      

      【讨论】:

        【解决方案8】:

        基于Eric's answer的扩展方法:

        public static IEnumerable<IEnumerable<T>> SplitIntoChunks<T>(this T[] source, int chunkSize)
        {
            var chunks = from index in Enumerable.Range(0, source.Length)
                         group source[index] by index / chunkSize;
        
            return chunks;
        }
        
        public static T[][] SplitIntoArrayChunks<T>(this T[] source, int chunkSize)
        {
            var chunks = from index in Enumerable.Range(0, source.Length)
                         group source[index] by index / chunkSize;
        
            return chunks.Select(e => e.ToArray()).ToArray();
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-03-03
          • 1970-01-01
          • 1970-01-01
          • 2011-05-11
          相关资源
          最近更新 更多