Question

So this is what I have for my list.

[0], [1], [2], [3], [4]

I want to be able to loop through these - But the trick here is that I want to start at an offset then loop around back to that offset?

ex.

[0], [1], [2], [3], [4]
      o-->            
//Start at offset 1 then get 2, 3, 4 then loop back around to zero

ex2.

[0], [1], [2], [3], [4]
                o-->            
//Start at offset 3 then get 4, then loop back around to zero, then 1, 2 

I thought about using the regular List<T> and trying to implement this concept into a for loop but I'm not sure if I want to do that if theirs a more concise way of doing so.

Basically don't start at 0 and loop back to the start and go through the elements back to the offset.

Was it helpful?

Solution

You are really describing a ring buffer or circular buffer.

http://en.wikipedia.org/wiki/Circular_buffer

The simple implementation is

int start; // Set your desired start offset

for (int i = start; i < myList.Length; i++)
{
    // do stuff
}

for (int j = 0; j < start; j++)
{
    // do stuff
}

OTHER TIPS

Here's my C# implementation using an extensions method with (i think) all of my dependencies as well.

I know this seems like a lot of code to post for such a simple thing, but it's mostly validation code and it was extremely critical in the application I am writing to work flawlessly with well defined behaviour. Lots and lots of other methods build on top of these two functions.

Performance from iterating in this way seems pretty good too, it's never been a bottleneck relative to the operations applied to the iterated values.

Span and SpanRange Extension Methods

using System;
using System.Collections.Generic;
using Common.FluentValidation;

namespace Common.Extensions
{
    public static partial class ExtensionMethods
    {
        /// <summary>
        /// Gets the index for an array relative to an anchor point, seamlessly crossing array boundaries in either direction.
        /// Returns calculated index value of an element within a collection as if the collection was a ring of contiguous elements (Ring Buffer).
        /// </summary>
        /// <param name="p_rollover">Index value after which the iterator should return back to zero.</param>
        /// <param name="p_anchor">A fixed or variable position to offset the iteration from.</param>
        /// <param name="p_offset">A fixed or variable position to offset from the anchor.</param>
        /// <returns>calculated index value of an element within a collection as if the collection was a ring of contiguous elements (Ring Buffer).</returns>
        public static int Span(this int p_rollover, int p_anchor, int p_offset)
        {
            // Prevent absolute value of `n` from being larger than count
            int n = (p_anchor + p_offset) % p_rollover;

            // If `n` is negative, then result is n less than rollover
            if (n < 0)
                n = n + p_rollover;

            return n;
        }

        /// <summary>
        /// Iterates over a collection from a specified start position to an inclusive end position. Iterator always increments
        /// from start to end, treating the original collection as a contiguous ring of items to be projected into a new form.
        /// Returns a projected collection of items that can contain all of the items from the original collection or a subset of the items.
        /// The first item in the projected collection will be the item from the original collection at index position p_first.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_collection">The collection to project into a new form.</param>
        /// <param name="p_first">Zero based index value of the first element in the projected sequence.</param>
        /// <param name="p_last">Zero based index value of the last element in the projected sequence.</param>
        /// <returns>a projected collection of items that can contain all of the items from the original collection or a subset of the items.
        /// The first item in the projected collection will be the item from the original collection at index position p_first.</returns>
        public static IEnumerable<T> SpanRange<T>(this IList<T> p_collection, int p_first, int p_last)
        {
            // Validate
            p_collection
                .CannotBeNullOrEmpty("p_collection");
            p_first
                .MustBeWithinRange(0, p_collection.Count - 1, "p_first");
            p_last
                .MustBeWithinRange(0, p_collection.Count - 1, "p_last");

            // Init
            int Rollover = p_collection.Count;
            int Count = (p_first <= p_last) ? p_last - p_first : (Rollover - p_first) + p_last;

            // Iterate
            for (int i = 0; i <= Count; i++)
            {
                var n = Rollover.Span(p_first, i);
                yield return p_collection[n];
            }
        }
    }
}

CannotBeNullOrEmpty Dependancy

using System;
using System.Collections.Generic;
using System.Linq;

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <summary>
        /// Validates the passed in parameter is not null or an empty collection, throwing a detailed exception message if the test fails.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_name">Name of tested parameter to assist with debugging.</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public static void CannotBeNullOrEmpty<T>(this ICollection<T> p_parameter, string p_name)
        {
            if (p_parameter == null)
                throw
                    new
                        ArgumentNullException(string.Format(
                        "The collection \"{0}\" cannot be null.",
                        p_name), default(Exception));

            if (p_parameter.Count <= 0)
                throw
                    new
                        ArgumentOutOfRangeException(string.Format(
                        "The collection \"{0}\" cannot be empty.",
                        p_name), default(Exception));
        }

        /// <summary>
        /// Validates the passed in parameter is not null or an empty collection, throwing a detailed exception message if the test fails.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_name">Name of tested parameter to assist with debugging.</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public static void CannotBeNullOrEmpty<T>(this IEnumerable<T> p_parameter, string p_name)
        {
            if (p_parameter == null)
                throw
                    new
                        ArgumentNullException(string.Format(
                        "The collection \"{0}\" cannot be null.",
                        p_name), default(Exception));

            if (p_parameter.Count() <= 0)
                throw
                    new
                        ArgumentOutOfRangeException(string.Format(
                        "The collection \"{0}\" cannot be empty.",
                        p_name), default(Exception));
        }

        /// <summary>
        /// Validates the passed in parameter is not null or empty, throwing a detailed exception message if the test fails.
        /// </summary>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_name">Name of tested parameter to assist with debugging.</param>
        /// <exception cref="ArgumentException"></exception>
        public static void CannotBeNullOrEmpty(this string p_parameter, string p_name)
        {
            if (string.IsNullOrEmpty(p_parameter))
                throw
                    new
                        ArgumentException(string.Format(
                        "The string \"{0}\" cannot be null or empty.",
                        p_name), default(Exception));
        }
    }
}

MustBeWithinRange Dependancy

using System;

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <summary>
        /// Validates the passed in parameter is within a specified range of values, throwing a detailed exception message if the test fails.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_minInclusive">The minimum valid value of the parameter.</param>
        /// <param name="p_maxInclusive">The maximum valid value of the parameter.</param>
        /// <param name="p_name">Name of tested parameter to assist with debugging.</param>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public static void MustBeWithinRange<T>(this IComparable<T> p_parameter, T p_minInclusive, T p_maxInclusive, string p_name)
            where T : struct
        {
            if (p_parameter.CompareTo(p_minInclusive) == -1)
                throw
                    new
                        ArgumentOutOfRangeException(string.Format(
                        "Parameter cannot be less than {0}, but \"{1}\" was {2}.",
                        p_minInclusive, p_name, p_parameter), default(Exception));

            if (p_parameter.CompareTo(p_maxInclusive) == 1)
                throw
                    new
                        ArgumentOutOfRangeException(string.Format(
                        "Parameter cannot be greater than {0}, but \"{1}\" was {2}.",
                        p_maxInclusive, p_name, p_parameter), default(Exception));
        }
    }
}

NOTE: It's too much code to include my Unit Test dependencies, so hopefully the verbosity of the Test case names can get the point across if you have your own similar extension methods, or just comment out things like my Print() statement which just dump all the values to a StringBuilder in a semi-intelligent way. You will need FluentValidation available from NuGet, or just replace the ...Should().Be(...) with your standard Assert(...) methods.

Unit Tests Span (NUnit framework)

using System;
using System.Text;
using Common.Extensions;
using Common.FluentValidation;
using FluentAssertions;
using NUnit.Framework;

namespace UnitTests.CommonTests.Extensions
{
    [TestFixture, Timeout(1000)]
    public class Span_Tests
    {
        // Test Anchoring
        [Test]
        public void Span_10_anchored_at_0_with_no_offset_should_be_0_Tests()
        {
            10.Span(0, 0).Should().Be(0);
        }

        [Test]
        public void Span_10_anchored_at_1_with_no_offset_should_be_1_Tests()
        {
            10.Span(1, 0).Should().Be(1);
        }

        [Test]
        public void Span_10_anchored_at_negative_1_with_no_offset_should_be_9_Tests()
        {
            10.Span(-1, 0).Should().Be(9);
        }

        [Test]
        public void Span_10_anchored_at_negative_10_with_no_offset_should_be_0_Tests()
        {
            10.Span(-10, 0).Should().Be(0);
        }

        // Test Offset
        [Test]
        public void Span_10_anchored_at_0_with_offset_of_1_should_be_1_Tests()
        {
            10.Span(0, 1).Should().Be(1);
        }

        [Test]
        public void Span_10_anchored_at_0_with_offset_of_negative_1_should_be_9_Tests()
        {
            10.Span(0, -1).Should().Be(9);
        }

        [Test]
        public void Span_10_anchored_at_0_with_offset_of_negative_10_should_be_0_Tests()
        {
            10.Span(0, -10).Should().Be(0);
        }

        // Test Iterations
        [Test]
        public void Span_array_of_10_anchored_at_0_can_walk_forward_thru_elements_twice_Tests()
        {
            var sb = new StringBuilder();

            try
            {
                var SequentialData = new int[10];
                for (int i = 0; i < SequentialData.Length; i++)
                    SequentialData[i] = i;

                sb.AppendFormat("SequentialData:\r\n{0}", SequentialData.Print());

                for (int i = 0; i < 20; i++)
                {
                    var n = SequentialData.Length.Span(0, i);

                    SequentialData[n].Should().Be(i % 10);
                }
            }
            catch (Exception)
            {
                Console.WriteLine(sb.ToString());
                throw;
            }
        }

        [Test]
        public void Span_array_of_10_anchored_at_0_can_walk_backwards_thru_elements_twice_Tests()
        {
            var sb = new StringBuilder();

            try
            {
                var SequentialData = new int[10];
                for (int i = 0; i < SequentialData.Length; i++)
                    SequentialData[i] = i;

                sb.AppendFormat("SequentialData:\r\n{0}", SequentialData.Print());

                for (int i = 0; i > -20; i--)
                {
                    var n = SequentialData.Length.Span(0, i);
                    var j = (i % 10);

                    switch (j)
                    {
                        case 0: SequentialData[n].Should().Be(0); break;
                        case -1: SequentialData[n].Should().Be(9); break;
                        case -2: SequentialData[n].Should().Be(8); break;
                        case -3: SequentialData[n].Should().Be(7); break;
                        case -4: SequentialData[n].Should().Be(6); break;
                        case -5: SequentialData[n].Should().Be(5); break;
                        case -6: SequentialData[n].Should().Be(4); break;
                        case -7: SequentialData[n].Should().Be(3); break;
                        case -8: SequentialData[n].Should().Be(2); break;
                        case -9: SequentialData[n].Should().Be(1); break;

                        default:
                            throw
                                j.ToString().CannotBeSwitchedToDefault("j");
                    }
                }
            }
            catch (Exception)
            {
                Console.WriteLine(sb.ToString());
                throw;
            }
        }
    }
}

Unit Tests SpanRange (NUnit framework)

using System;
using System.Collections.Generic;
using System.Text;
using Common.Extensions;
using FluentAssertions;
using NUnit.Framework;

namespace UnitTests.CommonTests.Extensions
{
    [TestFixture, Timeout(1000)]
    public class SpanRange_Tests
    {
        [Test]
        public void SpanRange_array_of_10_from_0_to_9_should_be_0_thru_9_Tests()
        {
            var sb = new StringBuilder();

            try
            {
                var Expected = new List<int>() { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

                var SequentialData = new int[10];
                for (int i = 0; i < SequentialData.Length; i++)
                    SequentialData[i] = i;

                sb.AppendFormat("SequentialData:\r\n{0}", SequentialData.Print());
                sb.AppendLine();

                var Range = new List<int>(SequentialData.SpanRange(0, 9));

                sb.AppendFormat("Range:\r\n{0}", Range.Print());

                Range.ShouldBeEquivalentTo(Expected);
            }
            catch (Exception)
            {
                Console.WriteLine(sb.ToString());
                throw;
            }
        }

        [Test]
        public void SpanRange_array_of_10_from_0_to_9_should_not_throw_ArgumentOutOfRangeException_Tests()
        {
            foreach (var item in new int[10].SpanRange(0, 9))
            { }
        }

        [Test, ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void SpanRange_array_of_10_from_0_to_10_should_throw_ArgumentOutOfRangeException_Tests()
        {
            foreach (var item in new int[10].SpanRange(0, 10))
            { }
        }

        [Test, ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void SpanRange_array_of_10_from_negative_1_to_9_should_throw_ArgumentOutOfRangeException_Tests()
        {
            foreach (var item in new int[10].SpanRange(-1, 9))
            { }
        }

        [Test]
        public void SpanRange_array_of_10_from_1_to_0_should_be_1_thru_9_then_0_Tests()
        {
            var sb = new StringBuilder();

            try
            {
                var Expected = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };

                var SequentialData = new int[10];
                for (int i = 0; i < SequentialData.Length; i++)
                    SequentialData[i] = i;

                sb.AppendFormat("SequentialData:\r\n{0}", SequentialData.Print());
                sb.AppendLine();

                var Range = new List<int>(SequentialData.SpanRange(1, 0));

                sb.AppendFormat("Range:\r\n{0}", Range.Print());

                Range.ShouldBeEquivalentTo(Expected);
            }
            catch (Exception)
            {
                Console.WriteLine(sb.ToString());
                throw;
            }
        }

        [Test]
        public void SpanRange_array_of_10_from_1_to_3_should_be_1_thru_3_Tests()
        {
            var sb = new StringBuilder();

            try
            {
                var Expected = new List<int>() { 1, 2, 3 };

                var SequentialData = new int[10];
                for (int i = 0; i < SequentialData.Length; i++)
                    SequentialData[i] = i;

                sb.AppendFormat("SequentialData:\r\n{0}", SequentialData.Print());
                sb.AppendLine();

                var Range = new List<int>(SequentialData.SpanRange(1, 3));

                sb.AppendFormat("Range:\r\n{0}", Range.Print());

                Range.ShouldBeEquivalentTo(Expected);
            }
            catch (Exception)
            {
                Console.WriteLine(sb.ToString());
                throw;
            }
        }

        [Test]
        public void SpanRange_array_of_10_from_9_to_1_should_be_9_0_and_1_Tests()
        {
            var sb = new StringBuilder();

            try
            {
                var Expected = new List<int>() { 9, 0, 1 };

                var SequentialData = new int[10];
                for (int i = 0; i < SequentialData.Length; i++)
                    SequentialData[i] = i;

                sb.AppendFormat("SequentialData:\r\n{0}", SequentialData.Print());
                sb.AppendLine();

                var Range = new List<int>(SequentialData.SpanRange(9, 1));

                sb.AppendFormat("Range:\r\n{0}", Range.Print());

                Range.ShouldBeEquivalentTo(Expected);
            }
            catch (Exception)
            {
                Console.WriteLine(sb.ToString());
                throw;
            }
        }

        [Test]
        public void SpanRange_array_of_5_from_1_to_4_should_be_1_thru_4_Tests()
        {
            var sb = new StringBuilder();

            try
            {
                var Expected = new List<int>() { 1, 2, 3, 4 };

                var SequentialData = new int[5];
                for (int i = 0; i < SequentialData.Length; i++)
                    SequentialData[i] = i;

                sb.AppendFormat("SequentialData:\r\n{0}", SequentialData.Print());
                sb.AppendLine();

                var Range = new List<int>(SequentialData.SpanRange(1, 4));

                sb.AppendFormat("Range:\r\n{0}", Range.Print());

                Range.ShouldBeEquivalentTo(Expected);
            }
            catch (Exception)
            {
                Console.WriteLine(sb.ToString());
                throw;
            }
        }

        [Test]
        public void SpanRange_array_of_5_from_0_to_0_should_be_0_Tests()
        {
            var sb = new StringBuilder();

            try
            {
                var Expected = new List<int>() { 0 };

                var SequentialData = new int[5];
                for (int i = 0; i < SequentialData.Length; i++)
                    SequentialData[i] = i;

                sb.AppendFormat("SequentialData:\r\n{0}", SequentialData.Print());
                sb.AppendLine();

                var Range = new List<int>(SequentialData.SpanRange(0, 0));

                sb.AppendFormat("Range:\r\n{0}", Range.Print());

                Range.ShouldBeEquivalentTo(Expected);
            }
            catch (Exception)
            {
                Console.WriteLine(sb.ToString());
                throw;
            }
        }

        [Test]
        public void SpanRange_array_of_5_from_1_to_1_should_be_1_Tests()
        {
            var sb = new StringBuilder();

            try
            {
                var Expected = new List<int>() { 1 };

                var SequentialData = new int[5];
                for (int i = 0; i < SequentialData.Length; i++)
                    SequentialData[i] = i;

                sb.AppendFormat("SequentialData:\r\n{0}", SequentialData.Print());
                sb.AppendLine();

                var Range = new List<int>(SequentialData.SpanRange(1, 1));

                sb.AppendFormat("Range:\r\n{0}", Range.Print());

                Range.ShouldBeEquivalentTo(Expected);
            }
            catch (Exception)
            {
                Console.WriteLine(sb.ToString());
                throw;
            }
        }

        [Test]
        public void SpanRange_array_of_5_from_4_to_0_should_be_4_then_0_Tests()
        {
            var sb = new StringBuilder();

            try
            {
                var Expected = new List<int>() { 4, 0 };

                var SequentialData = new int[5];
                for (int i = 0; i < SequentialData.Length; i++)
                    SequentialData[i] = i;

                sb.AppendFormat("SequentialData:\r\n{0}", SequentialData.Print());
                sb.AppendLine();

                var Range = new List<int>(SequentialData.SpanRange(4, 0));

                sb.AppendFormat("Range:\r\n{0}", Range.Print());

                Range.ShouldBeEquivalentTo(Expected);
            }
            catch (Exception)
            {
                Console.WriteLine(sb.ToString());
                throw;
            }
        }

        [Test]
        public void SpanRange_array_of_5_from_1_to_0_should_be_1_thru_4_then_0_Tests()
        {
            var sb = new StringBuilder();

            try
            {
                var Expected = new List<int>() { 1, 2, 3, 4, 0 };

                var SequentialData = new int[5];
                for (int i = 0; i < SequentialData.Length; i++)
                    SequentialData[i] = i;

                sb.AppendFormat("SequentialData:\r\n{0}", SequentialData.Print());
                sb.AppendLine();

                var Range = new List<int>(SequentialData.SpanRange(1, 0));

                sb.AppendFormat("Range:\r\n{0}", Range.Print());

                Range.ShouldBeEquivalentTo(Expected);
            }
            catch (Exception)
            {
                Console.WriteLine(sb.ToString());
                throw;
            }
        }
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top