morelinq / MoreLINQ

Extensions to LINQ to Objects
https://morelinq.github.io/
Apache License 2.0
3.63k stars 409 forks source link

`Rank` does not iterate source on re-iterations #1065

Closed atifaziz closed 2 months ago

atifaziz commented 2 months ago

The following code demonstrates this bug:

var xs = Enumerable.Range(1, 5)
                   .Select(x => x * 10)
                   .ToArray();

IEnumerable<int> Unstable()
{
    try
    {           
        foreach (var x in xs)
            yield return x;
    }
    finally
    {
        // rotate array elements by 1
        var x0 = xs[0];
        Array.Copy(xs, 1, xs, 0, xs.Length - 1);
        xs[^1] = x0;
    }
}

var result = Unstable().Rank();
foreach (var _ in xs)
    Console.WriteLine(result.ToDelimitedString(", "));

The output should have been:

5, 4, 3, 2, 1
4, 3, 2, 1, 5
3, 2, 1, 5, 4
2, 1, 5, 4, 3
1, 5, 4, 3, 2

Instead, it is:

5, 4, 3, 2, 1
5, 4, 3, 2, 1
5, 4, 3, 2, 1
5, 4, 3, 2, 1
5, 4, 3, 2, 1

It seems that Rank caches the source on the first iteration and reuses it on subsequent re-iterations. The culprit appears to be the following line:

https://github.com/morelinq/MoreLINQ/blob/9e8073d8b502fbc91697d3cdb25b97a23d71a6c4/MoreLinq/Rank.cs#L84

which modifies the original captured source.

This bug is present in the latest release (4.2).