【问题标题】:Can you enumerate a collection in C# out of order?你能在 C# 中乱序枚举一个集合吗?
【发布时间】:2010-09-20 06:05:30
【问题描述】:

有没有办法使用foreach 循环向后或以完全随机的顺序遍历集合?

【问题讨论】:

    标签: c# loops foreach


    【解决方案1】:

    你可以倒着做:

    for (int i=col.count-1; i>0; i--){ 
          DoSomething ( col.item[i]) ;
    }
    

    不确定确切的语法,但这是范式。

    至于完全随机的顺序,你可以通过它的索引来访问一个集合元素。为确保您点击了每个项目,您需要跟踪您已经处理了哪些元素(可能通过复制集合,然后在访问后删除元素)。

    编辑:随机访问的更多细节 随机访问的代码可能如下所示:

     collection c = originalCollection;
     while (c.count > 0) {
         int i = randomNumber(seed) mod c.count
         element d = c[i];
         c.remove(d);
         DoSomething(d);
    }
    

    【讨论】:

    • 你落后了。您需要使用 i-1 或 for (int i = col.Count - 1; i >= 0; i--)
    • @Domenic - 没有根本的用法差异@Jon B - 谢谢!
    • 您在随机示例中从原始集合中删除项目 - 除非您创建 if 的副本。
    • #Sam Meldrum - 随机示例的第一行是复制原始集合。
    • 呃,不,假,Collection大概是引用类型,所以确实是从原来的去掉了。
    【解决方案2】:

    我认为没有办法直接这样做,但 as good 使用通过 yield return 关键字返回新集合的扩展方法几乎是这样的。来自预先存在的图书馆;其他人指出 LINQ 有一个 Reverse 方法,像 OrderBy 这样的东西也可以工作。

    示例:如果您在IEnumerable<T> 上使用LINQ 扩展方法Reverse(),它使用yield return 以相反的顺序给出集合,那么执行foreach(var myThing in myCollection.Reverse()) 将以相反的顺序枚举集合。

    重要yield return 是关键。它的意思是“当我枚举这个集合时,然后去取东西。”与仅构造一个 new 的替代方案相反,反向收集效率非常低,并且可能有副作用。

    【讨论】:

      【解决方案3】:

      使用System.Linq 你可以...

      // List<...> list;
      foreach (var i in list.Reverse())
      {
      }
      

      对于随机顺序,您必须使用 list.OrderBy(另一个 Linq 扩展)对其进行随机排序,然后迭代该有序列表。

      var rnd = new Random();
      var randomlyOrdered = list.OrderBy(i => rnd.Next());
      foreach (var i in randomlyOrdered)
      {
      }
      

      【讨论】:

      • i=> rnd.Next() 有点危险——它假设每个项目只调用一次委托(即实现细节)。否则,排序可能会爆炸。碰巧的是,我认为您可以侥幸逃脱 - 但是首先明确选择(对临时对象)可能是可取的......
      • 另外,执行 OrderBy(g => Guid.NewGuid()) 速度更快(而且更随机)
      • 好点,我在 SQL Server 中随机排序查询结果时使用了相同的技术,但我什至没有想到在这里使用它。
      【解决方案4】:

      您可以通过提供自己的 Comparator 来对 List 进行排序并对其进行迭代。

      【讨论】:

        【解决方案5】:

        正如其他答案所提到的,Reverse() extension method 将让您以相反的顺序枚举一个序列。

        这是一个随机枚举扩展方法:

        public static IEnumerable<T> OrderRandomly<T>(this IEnumerable<T> sequence)
        {
            Random random = new Random();
            List<T> copy = sequence.ToList();
        
            while (copy.Count > 0)
            {
                int index = random.Next(copy.Count);
                yield return copy[index];
                copy.RemoveAt(index);
            }
        }
        

        你的用法是:

        foreach (int n in Enumerable.Range(1, 10).OrderRandomly())
            Console.WriteLine(n);
        

        【讨论】:

        • cfeduke 的 OrderBy 解决方案要好得多。
        • 虽然我必须承认我更喜欢扩展方法,因为真的,扩展方法是我的锤子,我的代码中的所有问题都是钉子。说真的,它对我来说已经到了那个地步。如果只能私下声明扩展方法,我会在天堂。
        • 像这样使用 ListRemoveAt 具有 O(n^2) 复杂度。对于类似但 O(n) 的算法,请参阅此问题:stackoverflow.com/questions/4412405/…
        • @cfeduke 扩展方法可以声明为私有的,不是吗?
        【解决方案6】:

        你想对一个集合进行排序并与之交互吗?

        如果是,试试这个:

        Random rand = new Random(Environment.TickCount);
        
        test.Sort((string v1, string v2) => {
                        if (v1.Equals(v2))
                        {
                            return 0;
                        }
        
                        int x = rand.Next();
                        int y = rand.Next();
        
                        if (x == y)
                        {
                            return 0;
                        }
                        else if (x > y)
                        {
                            return 1;
                        }
        
                        return -1; 
                    });
        
        for (string item in test)
        {
          Console.WriteLn(item);
        }
        // Note that test is List<string>;
        

        【讨论】:

        • 呃,嗯? test.OrderBy(t => rand.Next()) 怎么样?当然,它不像你的那样是一种稳定的类型,但大概在那个方向上很容易修改。此外,初始化 Random 时,Environment.TickCount 是不必要的。
        • Domenic,我非常喜欢 (t => rand.Next()) 但我已经写了这个太大的例子 :) 在我的测试中,我需要使用 Environment.TickCount 因为没有它,我得到相同的结果太多次...
        【解决方案7】:

        根据我对 C# 语言规范的阅读,foreach 迭代语句取决于正在迭代的结构/类/接口,其中定义了 GetEnumerator() 函数。 GetEnumerator() 返回的对象必须将 MoveNext() 定义为成员函数。 MoveNext() 定义为在第一次调用时访问列表中的“第一个”对象,然后在后续调用中访问“下一个”对象,返回 true 直到列表中不存在其他元素,然后返回 false。

        Domenic 所指的特性 yield return 首次出现在规范的 2.0 版本中,并且似乎确实对此有用。对于 1.1 版,您唯一的选择是从您的基础派生一个新的结构/类/接口并覆盖 GetEnumerator() 以返回一个新的 IEnumerator,其中 MoveNext() 函数将遵循不同的规则来选择第一个集合元素和任何后续集合元素。

        我自己的建议是使用索引集合,然后使用带有适当索引计算的 for 循环(如果需要,可以使用随机数生成器,使用整数数组或其他一些技术来验证相同的索引value 不使用两次),如果你必须在实际实践中这样做。

        【讨论】:

          【解决方案8】:

          从 C# 2.0 开始,您可以使用 yield 关键字来非常轻松地实现自定义迭代器。您可以在 MSDN http://msdn.microsoft.com/en-us/library/9k7k7cf0.aspx 上阅读有关 yield 关键字的更多信息

          您可以将 yield 视为从循环内部返回值的能力,但您应该参考上面的链接以完整说明它们是什么以及它们可以做什么。

          我写了一个关于如何实现几个自定义迭代器的简短示例。我已经将它们实现为扩展方法 (http://msdn.microsoft.com/en-us/library/bb383977.aspx) 以使代码更加流畅,我还使用数组初始化器 (http://msdn.microsoft.com/en-us/library/aa664573.aspx) 来设置整数列表的初始值。

          扩展方法和数组初始化器都不是实现自定义迭代器所必需的,但它们是 c# 3.0 的好特性,有助于编写更简洁的代码

          这是我的例子。它展示了如何通过仅返回奇数、偶数、反转或完全随机方式的数字来迭代整数列表。

          using System;
          using System.Collections.Generic;
          using System.Linq;
          using System.Text;
          
          namespace ConsoleApplication1
          {
              class Program
              {
                  static void Main(string[] args)
                  {
                      List<int> ints = 
                          new List<int> { 1,2,3,4,5,6,7,8,9,10};
          
                      Console.WriteLine("Iterating over Odd numbers only.");
                      foreach (int i in ints.Odd())
                      {
                          Console.WriteLine(i);
                      }
          
                      Console.WriteLine("Iterating over Even numbers only.");
                      foreach (int i in ints.Even())
                      {
                          Console.WriteLine(i);
                      }
          
                      Console.WriteLine("Iterating over the list in reversed order.");
                      foreach (int i in ints.Reversed())
                      {
                          Console.WriteLine(i);
                      }
          
                      Console.WriteLine("Iterating over the list in random order.");
                      foreach (int i in ints.Random())
                      {
                          Console.WriteLine(i);
                      }
          
                      Console.ReadLine();
                  }
              }
          
              public static class ListExtensions
              {
                  /// <summary>
                  /// Iterates over the list only returns even numbers
                  /// </summary>
                  /// <param name="list"></param>
                  public static IEnumerable<int> Even(this List<int> list)
                  {
                      foreach (var i in list)
                      {
                          if (i % 2 == 0)
                           {
                              yield return i;
                          }
                     }
                  }
          
                  /// <summary>
                  /// Iterates over the list only returns odd numbers
                  /// </summary>
                  public static IEnumerable<int> Odd(this List<int> list)
                  {
                      foreach (var i in list)
                      {
                          if (i % 2 != 0)
                          {
                              yield return i;
                          }
                      }
                  }
          
                  /// <summary>
                  /// Iterates over the list in reversed order
                  /// </summary>
                  public static IEnumerable<int> Reversed(this List<int> list)
                  {
                      for (int i = list.Count; i >= 0; i--)
                      {
                          yield return i;
                      }
                  }
          
                  /// <summary>
                  /// Iterates over the list in random order
                  /// </summary>
                  public static IEnumerable<int> Random(this List<int> list)
                  {
                      // Initialize a random number generator with a seed.
                      System.Random rnd =
                          new Random((int)DateTime.Now.Ticks);
          
                      // Create a list to keep track of which indexes we've
                      // already returned
                      List<int> visited =
                          new List<int>();
          
                      // loop until we've returned the value of all indexes
                      // in the list
                      while (visited.Count < list.Count)
                      {
                          int index =
                              rnd.Next(0, list.Count);
          
                          // Make sure we've not returned it already
                          if (!visited.Contains(index))
                          {
                              visited.Add(index);
                              yield return list[index];
                          }
                      }
                  }
              }
          }
          

          【讨论】:

          • 我对你的 Random() 枚举器有一种非常不好的感觉。是不是非常低效?假设要枚举的 List 有 100 万个条目。首先,您测试已返回的随机索引的方式涉及将条目添加到“已访问”列表中,以便它缓慢但肯定地增长,并多次重新分配。更糟糕的是,测试已返回的索引是线性搜索,因此“此索引是否已返回”这个简单问题可能涉及多达 100 万次比较,搜索“访问过的”列表。
          • 但这还不是最糟糕的部分。随着过程接近尾声,您正在生成越来越多的随机数,试图找到尚未返回的少数索引。具体来说,要“找到”第 100 万个索引,即您尚未返回的最后一个索引,我认为您可能必须生成数百万个随机数,并测试每个数字以确定它已被使用涉及 999,999 次比较。
          • visited 应该是一个 Set。这样Contains() 会更快。
          • 这绝不是生产级代码 - 这是一个简单的示例,我希望没有人将它投入生产:D
          【解决方案9】:

          我实际上很喜欢使用 LINQ 的 cfeduke 方法,但它让我很烦,因为它让我忘记了。添加到我之前的示例。如果你想在 LINQ 的帮助下进行奇偶迭代,你可以使用

          // Even
          foreach (var i in ints.FindAll(number => number % 2 == 0))
          {
                Console.WriteLine(i);
          }
          
          // Odd
          foreach (var i in ints.FindAll(number => number % 2 != 0))
          {
                Console.WriteLine(i);
          }
          

          【讨论】:

            【解决方案10】:

            使用C5 Generic Collection Library 中的IList&lt;T&gt;,反向迭代是一项功能,而不是扩展:

            foreach (var i in list.Reverse())
            {
            }
            

            同样,您可以使用Shuffle() 方法获得随机排序:

            var listClone = (IList<T>) list.Clone();
            listClone.Shuffle();
            foreach (var i in listClone)
            {
            }
            

            【讨论】:

              【解决方案11】:

              使用随机排序
              http://www.dailycoding.com/..using_linq.aspx

              List<Employee> list = new List<Employee>();
              
              list.Add(new Employee { Id = 1, Name = "Davolio Nancy" });
              list.Add(new Employee { Id = 2, Name = "Fuller Andrew" });
              list.Add(new Employee { Id = 3, Name = "Leverling Janet" });
              list.Add(new Employee { Id = 4, Name = "Peacock Margaret" });
              list.Add(new Employee { Id = 5, Name = "Buchanan Steven" });
              list.Add(new Employee { Id = 6, Name = "Suyama Michael" });
              list.Add(new Employee { Id = 7, Name = "King Robert" });
              list.Add(new Employee { Id = 8, Name = "Callahan Laura" });
              list.Add(new Employee { Id = 9, Name = "Dodsworth Anne" });
              
              list = list.OrderBy(emp => Guid.NewGuid()).ToList();
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2012-06-24
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2010-12-12
                • 1970-01-01
                相关资源
                最近更新 更多