c#IEnumerable双重迭代

时间:2012-12-04 13:04:25

标签: c# ienumerable

这是出于好奇我想问这个问题......

这是我的代码:

for (int i = 0; i < myList.Count - 1; ++i)
{
    for (int j = i+1; j < myList.Count; ++j)
    {
        DoMyStuff(myList[i], myList[j]);
    }
}

非常简单的循环,但显然它只适用于List ... 但我想知道...我怎么能编码这个循环,以使其独立于集合的类型(从IEnumerable派生......) 我的第一个想法是:

IEnumerator it1 = myList.GetEnumerator();
while (it1.MoveNext())
{
    IEnumerator it2 = it1; // this part is obviously wrong
    while (it2.MoveNext())
    {
        DoMyStuff(it1.Current, it2.Current);
    }
}

6 个答案:

答案 0 :(得分:3)

因为枚举器没有获得第n个元素的有效方法,所以最好的办法是将枚举复制到列表中,然后使用现有代码:

void CrossMap<T>(IEnumerable<T> enumerable)
{
    List<T> myList = enumerable.ToList();

    for (int i = 0; i < myList.Count - 1; ++i)
    {
        for (int j = i+1; j < myList.Count; ++j)
        {
            DoMyStuff(myList[i], myList[j]);
        }
    }
}

然而,对于某些集合类型,你可以做一个相当棘手的黑客攻击。因为BCL中某些集合类型的枚举器被声明为值类型而不是引用类型,所以您可以通过将枚举器复制到另一个变量来创建枚举器状态的隐式克隆:

// notice the struct constraint!
void CrossMap<TEnum, T>(TEnum enumerator) where TEnum : struct, IEnumerator<T>
{
    while (enumerator.MoveNext())
    {
        TEnum enum2 = enumerator;    // value type, so this makes an implicit clone!
        while (enum2.MoveNext())
        {
            DoMyStuff(enumerator.Current, enum2.Current);
        }
    }
}

// to use (you have to specify the type args exactly)
List<int> list = Enumerable.Range(0, 10).ToList();
CrossMap<List<int>.Enumerator, int>(list.GetEnumerator());

这是非常迟钝的,并且很难使用,因此只有在性能和空间要求严格时才应该这样做。

答案 1 :(得分:1)

Count()上声明了扩展方法ElementAt(int)IEnumerable<T>。它们在System.Linq命名空间中声明,如果您使用的是C#3之后的任何C#版本,默认情况下应该将其包含在.cs文件中。这意味着您可以这样做:

for (int i = 0; i < myList.Count() - 1; ++i)
{
  for (int j = i+1; j < myList.Count(); ++j)
  {
    DoMyStuff(myList.ElementAt(i), myList.ElementAt(j));
  }
}

但是,请注意这些是方法,并且会在迭代期间反复调用,因此您可能希望将结果保存到变量中,例如:

var elementCount = myList.Count();
for (int i = 0; i < elementCount - 1; ++i)
{
  var iElement = myList.ElementAt(i);
  for (int j = i+1; j < elementCount; ++j)
  {
    DoMyStuff(iElement, myList.ElementAt(j));
  }
}

您还可以尝试使用一些LINQ来选择符合条件的所有元素对,然后使用简单的foreach来调用处理,例如:

var result = myList.SelectMany((avalue, aindex) => 
               myList.Where((bvalue, bindex) => aindex < bindex)
                     .Select(bvalue => new {First = avalue, Second = bvalue}));

foreach (var item in result)
{
  DoMyStuff(item.First, item.Second);
}

答案 2 :(得分:1)

这是一种真正使用惰性IEnumerable范例从单个IEnumerable输入生成非重复组合流的方法。第一对将立即返回(没有缓存列表),但是在IEnumerable操作期间发生的延迟将会增加(除了非常高的n值或非常昂贵的Skip(n) s之外仍然难以察觉)外部调查员的一切进展:

public static IEnumerable<Tuple<T, T>> Combinate<T>(this IEnumerable<T> enumerable) {
    var outer = enumerable.GetEnumerator();
    var n = 1;
    while (outer.MoveNext()) {
        foreach (var item in enumerable.Skip(n))
            yield return Tuple.Create(outer.Current, item);
        n++;
    }
}

以下是您在案例中的使用方法:

foreach(var pair in mySource.Combinate())
    DoMyStuff(pair.Item1, pair.Item2);

<强>后记

每个人都指出(这里和其他地方)没有有效的方法来获得IEnumerable的“第n”元素。部分原因是IEnumerable甚至不需要基础源集合。例如,这是一个愚蠢的小函数,它可以为实验动态生成值,只要它们可以被消耗,并持续指定的时间,而不是任何计数:

public static IEnumerable<double> Sample(double milliseconds, Func<double> generator) {
    var sw = new Stopwatch();
    var timeout = TimeSpan.FromMilliseconds(milliseconds);
    sw.Start();
    while (sw.Elapsed < timeout)
        yield return generator();
}

答案 3 :(得分:0)

我反对IEnumerable<T>写一封并传递代表进行索引操作:

public static void DoStuff<T>(IEnumerable<T> seq, Func<int, T> selector)
{
    int count = seq.Count();
    for (int i = 0; i < count - 1; ++i)
    {
        for (int j = i+1; j < count; ++j)
        {
            DoMyStuff(selector(i), selector(j));
        }
    }
}

您可以使用以下方式调用它:

List<T> list = //whatever
DoStuff(list, i => list[i]);

如果将集合参数限制为ICollection<T>,则可以使用Count属性,而不是使用Count()扩展方法。

答案 4 :(得分:0)

效率不高,但可读:

int i = 0;
foreach( var item1 in myList)
{
    ++i;
    foreach( var item2 in myList.Skip(i))
        DoMyStuff(item1, item2);
}

答案 5 :(得分:0)

你可以使用IEnumerable.Skip()相当简洁地完成它,与将列表复制到数组中相比,它甚至可能相当快,因为​​列表足够短。但是,它必须比复制足够大小的列表慢很多。

您必须使用各种大小的列表进行一些计时,以查看复制到阵列的位置变得更有效。

这是代码。请注意,它正在迭代两次可枚举 - 如果正确实现了枚举,那就没问题了。

static void test(IEnumerable<int> myList)
{
    int n = 0;

    foreach (int v1 in myList)
    {
        foreach (int v2 in myList.Skip(++n))
        {
            DoMyStuff(v1, v2);
        }
    }
}