开发者

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

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;


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


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


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


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 :)


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


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
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜