CollectionTools.TakeLast()

Greg suggested in https://enduracode.kilnhg.com/Review/K185700
using Skip(length-n) instead of two reverses. The results were really
interesting.

Here’s our implementations

/// <summary>         /// Returns the last <paramref name="n"/> elements.         /// </summary>         public static IEnumerable<T> TakeLast<T>( this IEnumerable<T> c, int n ) {             return c.Reverse().Take( n ).Reverse();         }         /// <summary>         /// Returns the last <paramref name="n"/> elements.         /// </summary>         public static IEnumerable<T> TakeLast2<T>( this IEnumerable<T> c, int n ) {             var count = c.Count();             return c.Skip( count-n );         }

Here’s our performance test.

[ TestFixture ]
    internal class TakeLast {
        [ Test ]
        public void Test() {
            var enumerable = Enumerable.Range( 0, 1000000 );
            Console.WriteLine("Takelast");
            takelast( enumerable );
            takelast( enumerable );
            takelast( enumerable );

            Console.WriteLine("Takelast2");
            takelast2( enumerable );
            takelast2( enumerable );
            takelast2( enumerable );
            
            Assert.True( true );
        }

        private static void takelast( IEnumerable<int> enumerable ) {
            var stop = Stopwatch.StartNew();
            var last3 = enumerable.TakeLast( 10 );
            Console.WriteLine( "Took: " + stop.ElapsedTicks );
            Console.WriteLine( "Last: " + last3.GetCommaDelimitedStringFromCollection() );
        }
        
        private static void takelast2( IEnumerable<int> enumerable ) {
            var stop = Stopwatch.StartNew();
            var last3 = enumerable.TakeLast2( 10 );
            Console.WriteLine( "Took: " + stop.ElapsedTicks );
            Console.WriteLine( "Last: " + last3.GetCommaDelimitedStringFromCollection() );
        }
    }

Wait for it. Here’s the results of executing the test twice.

Takelast
Took: 3364
Last: 999990, 999991, 999992, 999993, 999994, 999995, 999996, 999997, 999998, 999999
Took: 1
Last: 999990, 999991, 999992, 999993, 999994, 999995, 999996, 999997, 999998, 999999
Took: 2
Last: 999990, 999991, 999992, 999993, 999994, 999995, 999996, 999997, 999998, 999999
Takelast2
Took: 17152
Last: 999990, 999991, 999992, 999993, 999994, 999995, 999996, 999997, 999998, 999999
Took: 14391
Last: 999990, 999991, 999992, 999993, 999994, 999995, 999996, 999997, 999998, 999999
Took: 14500
Last: 999990, 999991, 999992, 999993, 999994, 999995, 999996, 999997, 999998, 999999

Try #2
Takelast
Took: 3491
Last: 999990, 999991, 999992, 999993, 999994, 999995, 999996, 999997, 999998, 999999
Took: 1
Last: 999990, 999991, 999992, 999993, 999994, 999995, 999996, 999997, 999998, 999999
Took: 1
Last: 999990, 999991, 999992, 999993, 999994, 999995, 999996, 999997, 999998, 999999
Takelast2
Took: 16823
Last: 999990, 999991, 999992, 999993, 999994, 999995, 999996, 999997, 999998, 999999
Took: 14640
Last: 999990, 999991, 999992, 999993, 999994, 999995, 999996, 999997, 999998, 999999
Took: 14721
Last: 999990, 999991, 999992, 999993, 999994, 999995, 999996, 999997, 999998, 999999


Okay. Skip(length-n) is definitely not faster. What’s way more interesting to
me: why are the next two executions of the first test literally O(1)?

I don’t know. That’s a good question for Stack Overflow.

The conclusion from Stack Overflow is that my test was flawed and that Greg’s suggestion is faster. It appeared my solution was faster due to deferred execution.