你能在 C# 中无序枚举一个集合吗?

Can you enumerate a collection in C# out of order?

提问人:Jason Z 提问时间:11/1/2008 最后编辑:swilliamsJason Z 更新时间:4/7/2015 访问量:7473

问:

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

C# 循环 foreach

评论


答:

0赞 Stephen Wrighton 11/1/2008 #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);
}

评论

0赞 Jon B 11/1/2008
你差一个。您需要使用 i-1 或 do for (int i = col.计数 - 1;我 >= 0;i--)
0赞 Stephen Wrighton 11/1/2008
@Domenic - @Jon B 没有根本的用法差异 - 谢谢!
0赞 Sam Meldrum 11/1/2008
在随机示例中,您正在从原始集合中删除项 - 除非您创建 if 的副本。
0赞 Stephen Wrighton 11/1/2008
#Sam Meldrum - 随机示例的第一行是复制原始集合。
0赞 Domenic 11/1/2008
呃,不,假,Collection 大概是引用类型,所以它确实从原始类型中删除了。
1赞 Domenic 11/1/2008 #2

我认为没有办法直接这样做,但几乎就是使用通过 yield return 关键字返回新集合的扩展方法。这些可能来自预先存在的库;其他人指出 LINQ 有一种方法,类似的东西也行得通。as goodReverseOrderBy

示例:如果在 上使用 LINQ 扩展方法,该方法用于以相反的顺序提供集合,则执行 将以相反的顺序枚举集合。Reverse()IEnumerable<T>yield returnforeach(var myThing in myCollection.Reverse())

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

16赞 cfeduke 11/1/2008 #3

使用你可以做...System.Linq

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

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

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

评论

3赞 Marc Gravell 11/1/2008
i=> rnd.Next() 有点危险 - 它假设每个项目只调用一次委托(即实现细节)。否则,排序可能会爆炸。碰巧的是,我认为你可以侥幸逃脱 - 但显式 Select first(到临时对象)可能是建议的......
3赞 Kirschstein 2/8/2010
此外,执行 OrderBy(g => Guid.NewGuid()) 的速度更快(也更随机)
0赞 cfeduke 2/8/2010
好点子,当我在 SQL Server 中随机排序查询结果时,我使用了相同的技术,但我甚至没有想到在这里使用它。
0赞 Tigraine 11/1/2008 #4

您可以通过提供自己的比较器来对列表进行排序,并遍历该比较器。

15赞 Jacob Carpenter 11/1/2008 #5

正如其他答案所提到的,Reverse() 扩展方法将允许您以相反的顺序枚举序列。

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

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

评论

0赞 Domenic 11/1/2008
cfeduke 的 OrderBy 解决方案要好得多。
0赞 cfeduke 11/1/2008
虽然我必须承认我更喜欢扩展方法,因为实际上,扩展方法是我的锤子,我代码中的所有问题都是钉子。说真的,对我来说已经到了那个地步。如果只能私下宣布扩展方法,我会在天堂。
2赞 Matthew 12/11/2010
使用和类似具有 O(n^2) 的复杂性。对于类似但 O(n) 的算法,请参阅以下问题:stackoverflow.com/questions/4412405/...ListRemoveAt
0赞 nawfal 5/30/2013
@cfeduke扩展方法可以声明为私有的,不是吗?
0赞 Zote 11/1/2008 #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>;

评论

0赞 Domenic 11/1/2008
呃,呃?测试怎么样。OrderBy(t =>兰特。Next())?诚然,它不像你那样稳定,但想必它很容易朝这个方向修改。此外,初始化 Random 时不需要 Environment.TickCount。
0赞 Zote 11/3/2008
多梅尼克,我非常喜欢(t =>兰特。Next()) 但我已经写了这个太大的例子:)在我的测试中,我需要使用 Environment.TickCount,因为如果没有它,我得到相同的结果太多次了......
0赞 cpkilekofp 11/1/2008 #7

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

Domenic 所指的特性,即收益回报,首次出现在规范的 2.0 版本中,并且似乎对这个目的很有用。对于版本 1.1,唯一的选择是从基础派生一个新的结构/类/接口,并重写 GetEnumerator() 以返回新的 IEnumerator,其中 MoveNext() 函数在选择第一个集合元素和任何后续集合元素时将遵循不同的规则。

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

1赞 TheCodeJunkie 11/1/2008 #8

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

您可以将收益视为从循环内部返回值的能力,但您应该参考上面的链接,以全面了解它们是什么以及它们可以做什么。

我写了一个关于如何实现几个自定义迭代器的简短示例。我已将它们实现为扩展方法 (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];
                }
            }
        }
    }
}

评论

0赞 RenniePet 4/7/2015
我对你的 Random() 枚举器有一种非常糟糕的感觉。效率是不是非常低?假设要枚举的列表有 100 万个条目。首先,测试已返回的随机索引的方法包括将条目添加到“访问”列表,以便它缓慢但肯定地增长,并被重新分配多次。更糟糕的是,对已返回的索引的测试是线性搜索,因此简单的问题“此索引是否已返回”可能涉及多达 100 万次比较,搜索“已访问”列表。
0赞 RenniePet 4/7/2015
但这还不是最糟糕的部分。随着该过程接近尾声,您将生成越来越多的随机数,试图找到尚未返回的几个索引。具体来说,要“找到”第 100 万个索引,即您尚未返回的最后一个索引,我认为您可能需要生成数百万个随机数,并测试这些数字中的每一个以确定它是否已被使用涉及 999,999 次比较。
0赞 Mariusz Jamro 2/2/2017
visited应为Set。这样会更快。Contains()
0赞 TheCodeJunkie 2/3/2017
这绝不是生产级代码 - 这是一个简单的示例,我希望没有人将其投入生产:D
1赞 TheCodeJunkie 11/1/2008 #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);
}
1赞 Marcus Griep 11/1/2008 #10

使用 C5 泛型集合库中的反向迭代是一项功能,而不是扩展:IList<T>

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

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

var listClone = (IList<T>) list.Clone();
listClone.Shuffle();
foreach (var i in listClone)
{
}
0赞 Ramesh Soni 11/21/2008 #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();