Is there an equivalent to Python's enumerate() for .NET IEnumerable
Asked Answered
O

6

23

I could not find a related question.

In python you can easily loop through a sequence (list, generator etc) and collect the index of the iteration at the same time thanks to enumerate(seq) like this :

>>> for (i,item) in enumerate(["toto","titi","tutu"]):
...     print i, item
...
0 toto
1 titi
2 tutu

Is there something similar for IEnumerable, that would, for instance, transform a IEnumerable<T> in a IEnumerable<Tuple<Int32,T>> ?

(I know it would be easily done thanks to the correct function in Select() .. but if it exists, I'd rather use it :) )

UPDATE FYI, I am curious about this kind of possibility to be able to do something like : "give me the index of the last item that fulfils this condition", which would then be accomplished through :

myEnumeration.First(t => some condition on t.Item2 ... ).Item1;
Ointment answered 12/9, 2011 at 13:56 Comment(2)
Maybe adding this for completeness. Here is a way using desconstruct : twitter.com/buhakmeh/status/1291029712458911752?s=20Ointment
Also just discovered twitter.com/SergioPedri/status/1291330327881879552?s=20 . Microsoft.Toolkit.HighPerformance has a 'Enumerate()' extension methodOintment
L
8

Instead of using a Tuple<,> (which is a class) you can use a KeyValuePair<,> which is a struct. This will avoid memory allocations when enumerated (not that they are very expensive, but still).

public static IEnumerable<KeyValuePair<int, T>> Enumerate<T>(this IEnumerable<T> items) {
    return items.Select((item, key) => new KeyValuePair(key, item));
}
Lappet answered 12/9, 2011 at 14:28 Comment(6)
thanks, this is actually what I am using right now, but it feels a bit awkward (specially when written in VB.NET ... but what code involving generics in VB.NET looks nice ? )Ointment
to illustrate : Dim lastAccountDiscountIndex = availableDiscounts.Select(Function(d, i) New KeyValuePair(Of GlobalDiscountDefaultValue, Int32)(d, i)) _ .Last(Function(kvp) kvp.Key.Source.Type = GlobalDiscountSourceType.QuotationAccount) _ .ValueOintment
Well, don't use VB.NET then ;) - I know that this isn't very constructive, but VB.NET has major syntax shortcomings IMHO in generics. BTW, as of .NET 4, the _ line combining character is no longer required in all cases, you should be able to at least drop those.Lappet
I wish I had a choice :'( . Actually I can not drop those underscores in that case . I could if I wanted to keep the dot at the end of each line, and the Method on the following one, but it just looks wrong to me ...Ointment
Yeah, I though so (that you didn't have the choice). Anyways, I'd prefer the dot at the end of the line (which is my style anyways g) compared to the continuation character and the dot at the beginning of the line. Sorry for not being more helpful.Lappet
I'll mark this one as the answer for the comment about KeyValuePair vs Tuple.Ointment
A
15

C# 7 finally allows you to do this in an elegant way:

static class Extensions
{
    public static IEnumerable<(int, T)> Enumerate<T>(
        this IEnumerable<T> input,
        int start = 0
    )
    {
        int i = start;
        foreach (var t in input)
            yield return (i++, t);
    }
}

class Program
{
    static void Main(string[] args)
    {
        var s = new string[]
        {
            "Alpha",
            "Bravo",
            "Charlie",
            "Delta"
        };

        foreach (var (i, o) in s.Enumerate())
            Console.WriteLine($"{i}: {o}");
    }
}
Applicatory answered 21/7, 2017 at 13:35 Comment(0)
L
8

Instead of using a Tuple<,> (which is a class) you can use a KeyValuePair<,> which is a struct. This will avoid memory allocations when enumerated (not that they are very expensive, but still).

public static IEnumerable<KeyValuePair<int, T>> Enumerate<T>(this IEnumerable<T> items) {
    return items.Select((item, key) => new KeyValuePair(key, item));
}
Lappet answered 12/9, 2011 at 14:28 Comment(6)
thanks, this is actually what I am using right now, but it feels a bit awkward (specially when written in VB.NET ... but what code involving generics in VB.NET looks nice ? )Ointment
to illustrate : Dim lastAccountDiscountIndex = availableDiscounts.Select(Function(d, i) New KeyValuePair(Of GlobalDiscountDefaultValue, Int32)(d, i)) _ .Last(Function(kvp) kvp.Key.Source.Type = GlobalDiscountSourceType.QuotationAccount) _ .ValueOintment
Well, don't use VB.NET then ;) - I know that this isn't very constructive, but VB.NET has major syntax shortcomings IMHO in generics. BTW, as of .NET 4, the _ line combining character is no longer required in all cases, you should be able to at least drop those.Lappet
I wish I had a choice :'( . Actually I can not drop those underscores in that case . I could if I wanted to keep the dot at the end of each line, and the Method on the following one, but it just looks wrong to me ...Ointment
Yeah, I though so (that you didn't have the choice). Anyways, I'd prefer the dot at the end of the line (which is my style anyways g) compared to the continuation character and the dot at the beginning of the line. Sorry for not being more helpful.Lappet
I'll mark this one as the answer for the comment about KeyValuePair vs Tuple.Ointment
T
5

As for a specific function that will do what you're asking, I don't know if .NET includes it. The quickest way, however, would just be to do something like this:

int id = 0;
foreach(var elem in someList)
{
    ... doStuff ...
    id++;
}

EDIT: Here is a function that will do as you ask, using yield return, but it has the downside of requiring one GC allocation per iteration:

public static IEnumerable<Tuple<int, T>> Enumerate<T>(IEnumerable<T> list)
{
    int id = 0;
    foreach(var elem in list)
    {
        yield return new Tuple<int, T>(id, elem);
        id++;
    }
}
Thanh answered 12/9, 2011 at 14:0 Comment(2)
+1 for the yield return which, I assume, is the same as returning something in the function in the Select() method. (see my answer below, the incrementing of index is done for you in one of the signatures of Select())Ointment
I'll mark the one with KeyValuePair as the answer because of the struct vs class thing. But thanks for this one :-)Ointment
O
4

Here is my own answer to my own question ...

If it does not exist, I might as well do it like that, without actually writing a for/foreach :

var items = new List<String> { "toto", "titi", "tutu" };

var enumerated = items.Select((x, i) => new Tuple<int, String>(i, x));

foreach (var t in enumerated)
{
    Console.WriteLine(String.Format("{0} : {1}", t.Item1, t.Item2));

}

which prints:

0 : toto
1 : titi
2 : tutu

It's a one-liner ... an ugly one, but a one-liner anyway :)

Ointment answered 12/9, 2011 at 14:28 Comment(0)
L
1

Here's an extension method, that will return sequence of tuples, where the first item is number in a sequence and the second item is the value from the source sequence.

public static IEnumerable<Tuple<int, T>> Enumerate<T>(this IEnumerable<T> source, int start = 0)
{
    return source.Select((value, index) => new Tuple<int, T>(index + start, value));
}

Examples:

var source = new[] { "apple", "pear", "banana", "orange", "lemon" };
var enumerated1 = source.Enumerate();   // { (0, "apple"), (1, "pear"), (2, "banana"), (3, "orange"), (4, "lemon") }
var enumerated2 = source.Enumerate(3);  // { (3, "apple"), (4, "pear"), (5, "banana"), (6, "orange"), (7, "lemon") }
Levorotation answered 27/9, 2019 at 9:47 Comment(1)
Code-only answers are generally frowned upon on this site. Could you please edit your answer to include some comments or explanation of your code? Explanations should answer questions like: What does it do? How does it do it? Where does it go? How does it solve OP's problem? See: How to anwser. Thanks!Jonell
O
0

How about using Zip with Enumerable.Range?

var list = new List<string> { "toto", "titi", "tutu" };
foreach (var (str, i) in list.Zip(Enumerable.Range(0, int.MaxValue)))
{
    Console.WriteLine(i + " " + str);
}

Output:

0 toto
1 titi
2 tutu
Orsino answered 25/1, 2023 at 1:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.