sumOut
sumOut

Reputation: 103

Take n elements. If at end start from begining

How can I take n elements from a m elements collection so that if I run out of elements it starts from the beginning?

List<int> list = new List<int>() {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
List<int> newList = list.Skip(9).Take(2).ToList();
List<int> expected = new List(){10,1};

CollectionAssert.AreEqual(expected, newList);

How can I get the expected list? I'm looking for a CircularTake() function or something in that direction.

Upvotes: 10

Views: 607

Answers (5)

Allrameest
Allrameest

Reputation: 4492

My take on a CircularTake extension.

public static IEnumerable<T> CircularTake<T>(this IReadOnlyList<T> source, int count)
{
    return Enumerable.Range(0, count).Select(i => source[i % source.Count]);
}

Upvotes: 2

Rufus L
Rufus L

Reputation: 37020

You don't need to track the overflow because we can use the % modulus operator (which returns the remainder from an integer division) to continually loop through a range of indexes, and it will always return a valid index in the collection, wrapping back to 0 when it gets to the end (and this will work for multiple wraps around the end of the list):

List<int> list = new List<int> {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
List<int> newList = new List<int>();

for (int skip = 9, take = 2; take > 0; skip++, take--)
{
    newList.Add(list[skip % list.Count]);
}

Result:

// newList == { 10, 1 }

enter image description here


This could be extracted into an extension method:

public static List<T> SkipTakeWrap<T>(this List<T> source, int skip, int take)
{
    var newList = new List<T>();

    while (take > 0)
    {
        newList.Add(source[skip % source.Count]);
        skip++;
        take--;
    }

    return newList;
}

And then it could be called like:

List<int> list = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
List<int> newList = list.SkipTakeWrap(9, 2);

Upvotes: 10

Sir Rufo
Sir Rufo

Reputation: 19096

use an extension method to circular repeat the enumerable

public static IEnumerable<T> Circular<T>( this IEnumerable<T> source )
{
    while (true)
        foreach (var item in source) 
            yield return item;
}

and you can use your code

List<int> list = new List<int>() {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
List<int> newList = list.Circular().Skip(9).Take(2).ToList();

.net fiddle example

Upvotes: 10

lem2802
lem2802

Reputation: 1162

you may need to do something like this

        var start = 9;
        var amount = 2;
        List<int> list = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
        List<int> listOverflow = list.ToList();

        var overflow = (start + amount) - list.Count;
        if (overflow > 0)
            for (var i = 0; i < overflow; i++)
                listOverflow.AddRange(list.ToList());

        var newList = listOverflow.Skip(start).Take(amount).ToList();

Upvotes: 3

bradib0y
bradib0y

Reputation: 1089

int overflow = take - (elements.Count - skip);

if(overflow > 0)
{
  results.AddRange(elements.Skip(skip).Take(take - overflow));
  results.AddRange(elements.Take(overflow));
}

If there is a possiblity that there are more than one circular iterations, e.g. from 3 elements, take 10 or more, then you can apply this logic in a recursive function.

Upvotes: 1

Related Questions