【发布时间】:2010-09-20 06:05:30
【问题描述】:
有没有办法使用foreach 循环向后或以完全随机的顺序遍历集合?
【问题讨论】:
有没有办法使用foreach 循环向后或以完全随机的顺序遍历集合?
【问题讨论】:
你可以倒着做:
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);
}
【讨论】:
我认为没有办法直接这样做,但 as good 使用通过 yield return 关键字返回新集合的扩展方法几乎是这样的。来自预先存在的图书馆;其他人指出 LINQ 有一个 Reverse 方法,像 OrderBy 这样的东西也可以工作。
示例:如果您在IEnumerable<T> 上使用LINQ 扩展方法Reverse(),它使用yield return 以相反的顺序给出集合,那么执行foreach(var myThing in myCollection.Reverse()) 将以相反的顺序枚举集合。
重要:yield return 是关键。它的意思是“当我枚举这个集合时,然后去取东西。”与仅构造一个 new 的替代方案相反,反向收集效率非常低,并且可能有副作用。
【讨论】:
使用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)
{
}
【讨论】:
您可以通过提供自己的 Comparator 来对 List 进行排序并对其进行迭代。
【讨论】:
正如其他答案所提到的,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);
【讨论】:
List 和 RemoveAt 具有 O(n^2) 复杂度。对于类似但 O(n) 的算法,请参阅此问题:stackoverflow.com/questions/4412405/…
你想对一个集合进行排序并与之交互吗?
如果是,试试这个:
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>;
【讨论】:
根据我对 C# 语言规范的阅读,foreach 迭代语句取决于正在迭代的结构/类/接口,其中定义了 GetEnumerator() 函数。 GetEnumerator() 返回的对象必须将 MoveNext() 定义为成员函数。 MoveNext() 定义为在第一次调用时访问列表中的“第一个”对象,然后在后续调用中访问“下一个”对象,返回 true 直到列表中不存在其他元素,然后返回 false。
Domenic 所指的特性 yield return 首次出现在规范的 2.0 版本中,并且似乎确实对此有用。对于 1.1 版,您唯一的选择是从您的基础派生一个新的结构/类/接口并覆盖 GetEnumerator() 以返回一个新的 IEnumerator,其中 MoveNext() 函数将遵循不同的规则来选择第一个集合元素和任何后续集合元素。
我自己的建议是使用索引集合,然后使用带有适当索引计算的 for 循环(如果需要,可以使用随机数生成器,使用整数数组或其他一些技术来验证相同的索引value 不使用两次),如果你必须在实际实践中这样做。
【讨论】:
从 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];
}
}
}
}
}
【讨论】:
Contains() 会更快。
我实际上很喜欢使用 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);
}
【讨论】:
使用C5 Generic Collection Library 中的IList<T>,反向迭代是一项功能,而不是扩展:
foreach (var i in list.Reverse())
{
}
同样,您可以使用Shuffle() 方法获得随机排序:
var listClone = (IList<T>) list.Clone();
listClone.Shuffle();
foreach (var i in listClone)
{
}
【讨论】:
使用随机排序
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();
【讨论】: