5

This is out of curiosity I want to ask this question...

Here is my code:

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

Pretty simple loop, but obviously it only works with List... But I was wondering... how can I code this loop in order to make it independent of the collection's type (deriving from IEnumerable...) My first thought:

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

6 回答 6

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

这是相当迟钝的,而且很难使用,所以你应该只在性能和空间关键的情况下这样做。

于 2012-12-04T13:09:41.970 回答
1

这是一种真正使用惰性范式从单个输入IEnumerable生成非重复组合流的方法。IEnumerable第一对将立即返回(不缓存列表),但在操作期间会增加延迟(除了非常高的 n 值或非常昂贵IEnumerable的 s 外,仍然难以察觉),Skip(n)这发生在外部枚举器上的每次向前移动之后:

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. 这部分是因为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();
}
于 2012-12-04T13:34:30.273 回答
1

有扩展方法Count(),并且ElementAt(int)IEnumerable<T>. 它们在System.Linq命名空间中声明,如果您使用的任何 C# 版本高于 C# 3,则默认情况下应将其包含在您的 .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,它将选择所有符合条件的元素对,然后使用 simpleforeach调用处理,例如:

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);
}
于 2012-12-04T13:06:52.097 回答
0

效率不高,但可读:

int i = 0;
foreach( var item1 in myList)
{
    ++i;
    foreach( var item2 in myList.Skip(i))
        DoMyStuff(item1, item2);
}
于 2012-12-04T13:33:35.410 回答
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()扩展方法。

于 2012-12-04T13:13:45.637 回答
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);
        }
    }
}
于 2012-12-04T13:35:33.710 回答