C # IEnumerable double iteration

It 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 a list ... But I was wondering ... how can I encode this loop to make it independent of the type of the collection (based on 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
source share
6 answers

Since counters do not have an effective way to get the nth element, it is best to copy the enumerable into a list, and then use the existing code:

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

However, there is a pretty dodgy hack that you can do with some types of collections. Since enumerations of some collection types in BCL are declared as value types and not reference types, you can create an implicit state clone of the enumerator by copying it to another variable:

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

It's pretty dumb and pretty hard to use, so you should only do this if it's performance and mission-critical area.

+3
source

There are extension methods Count() and ElementAt(int) declared on IEnumerable<T> . They are declared in the System.Linq namespace, which should be included by default in your .cs files if you use any version of C # later than C # 3. This means that you could just do:

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

However, note that these are methods and will be called again and again during the iteration, so you can save their result in variables, for example:

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

You can also try LINQ, which will select all pairs of matching elements, and then use a simple foreach to invoke processing, for example:

 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); } 
+1
source

Here is a way that the lazy IEnumerable paradigm will really be used to generate a stream of non-duplicated combinations from a single IEnumerable input. The first pair will immediately return (without caching lists), but delays will increase (still imperceptible, except for very high values ​​of n or very expensive IEnumerable s) during the operation Skip(n) , which occurs after each forward movement of the external counter:

 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++; } } 

Here is how you could use it in your case:

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

Postscript

Everyone noted (here and elsewhere) that there is no efficient way to get the "n-th" IEnumerable element. This is partly due to the fact that IEnumerable does not require a basic source collection. For example, here is a silly little function that dynamically generates values ​​for an experiment as quickly as they can be used, and lasts for a certain period of time, and not for any quantity:

 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(); } 
+1
source

I would write against IEnumerable<T> and pass in the delegate for the indexing operation:

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

You can call it using:

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

If you are restricting the collection argument to ICollection<T> , you can use the Count property instead of using the Count() extension method.

0
source

Not very effective, but readable:

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

You can do this briefly enough using IEnumerable.Skip (), and it can even be pretty fast compared to copying a list to an array if the list is short enough. However, it should be much slower than copying for lists of sufficient size.

You will need to do some timings with lists of various sizes to see how copying to an array becomes more efficient.

Here is the code. Please note that it repeats the enumeration twice - it will be normal if the enumeration is executed correctly!

 static void test(IEnumerable<int> myList) { int n = 0; foreach (int v1 in myList) { foreach (int v2 in myList.Skip(++n)) { DoMyStuff(v1, v2); } } } 
0
source

All Articles