views:

710

answers:

4

In python, I can take a list my_list and rotate the contents:

>>> my_list = list(range(10))
>>> my_list
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> new_list = my_list[1:] + my_list[:1]
>>> new_list
[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]

What's the equivalent way in C# to create a new list that is a made up of two slices of an existing C# list? I know I can generate by brute force if necessary.

+10  A: 

You can easily use LINQ to do this:

// Create the list
int[] my_list = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

IEnumerable<int> new_list =
    my_list.Skip(1).Concat(my_list.Take(1));

You could even add this as an extension method like so:

public static IEnumerable<T> Slice(this IEnumerable<T> e, int count)
{
     // Skip the first number of elements, and then take that same number of
     // elements from the beginning.
     return e.Skip(count).Concat(e.Take(count));
}

Of course there needs to be some error checking in the above, but that's the general premise.


Thinking about this more, there are definite improvements that can be made to this algorithm which would improve performance.

You can definitely take advantage if the IEnumerable<T> instance implements IList<T> or is an array, taking advantage of the fact that it is indexed.

Also, you can cut down on the number of iterations that are required to skip and take would take within the body of the message.

For example, if you have 200 items and you want to slice with a value of 199, then it requires 199 (for the initial skip) + 1 (for the remaining item) + 199 (for the take) iterations in the body of the Slice method. This can be cut down by iterating through the list once, storing the items in a list which is then concatenated to itself (requiring no iteration).

In this case, the trade off here is memory.

To that end, I propose the following for the extension method:

public static IEnumerable<T> Slice(this IEnumerable<T> source, int count)
{
    // If the enumeration is null, throw an exception.
    if (source == null) throw new ArgumentNullException("source");

    // Validate count.
    if (count < 0) throw new ArgumentOutOfRangeException("count", 
        "The count property must be a non-negative number.");

    // Short circuit, if the count is 0, just return the enumeration.
    if (count == 0) return source;

    // Is this an array?  If so, then take advantage of the fact it
    // is index based.
    if (source.GetType().IsArray)
    {
        // Return the array slice.
        return SliceArray((T[]) source, count);
    }

    // Check to see if it is a list.
    if (source is IList<T>)
    {
        // Return the list slice.
        return SliceList ((IList<T>) source);
    }

    // Slice everything else.
    return SliceEverything(source, count);
}

private static IEnumerable<T> SliceArray(T[] arr, int count)
{
     // Error checking has been done, but use diagnostics or code
     // contract checking here.
     Debug.Assert(arr != null);
     Debug.Assert(count > 0);

     // Return from the count to the end of the array.
     for (int index = count; index < arr.Length; index++)
     {
          // Return the items at the end.
          yield return arr[index];
     }

     // Get the items at the beginning.
     for (int index = 0; index < count; index++)
     {
          // Return the items from the beginning.
          yield return arr[index];          
     }
}

private static IEnumerable<T> SliceList(IList<T> list, int count)
{
     // Error checking has been done, but use diagnostics or code
     // contract checking here.
     Debug.Assert(list != null);
     Debug.Assert(count > 0);

     // Return from the count to the end of the list.
     for (int index = count; index < list.Count; index++)
     {
          // Return the items at the end.
          yield return list[index];
     }

     // Get the items at the beginning.
     for (int index = 0; index < list.Count; index++)
     {
          // Return the items from the beginning.
          yield return list[index];          
     }
}

// Helps with storing the sliced items.
internal class SliceHelper<T> : IEnumerable<T>
{
    // Creates a
    internal SliceHelper(IEnumerable<T> source, int count)
    {
        // Test assertions.
        Debug.Assert(source != null);
        Debug.Assert(count > 0);

        // Set up the backing store for the list of items
        // that are skipped.
        skippedItems = new List<T>(count);

        // Set the count and the source.
        this.count = count;
        this.source = source;
    }

    // The source.
    IEnumerable<T> source;

    // The count of items to slice.
    private int count;

    // The list of items that were skipped.
    private IList<T> skippedItems;

    // Expose the accessor for the skipped items.
    public IEnumerable<T> SkippedItems { get { return skippedItems; } }

    // Needed to implement IEnumerable<T>.
    // This is not supported.
    System.Collections.IEnumerator 
        System.Collections.IEnumerable.GetEnumerator()
    {
        throw new InvalidOperationException(
            "This operation is not supported.");
    }

    // Skips the items, but stores what is skipped in a list
    // which has capacity already set.
    public IEnumerator<T> GetEnumerator()
    {
        // The number of skipped items.  Set to the count.
        int skipped = count;

        // Cycle through the items.
        foreach (T item in source)
        {
            // If there are items left, store.
            if (skipped > 0)
            {
                // Store the item.
                skippedItems.Add(item);

                // Subtract one.
                skipped--;
            }
            else
            {
                // Yield the item.
                yield return item;
            }
        }
    }
}

private static IEnumerable<T> SliceEverything<T>(
    this IEnumerable<T> source, int count)
{
    // Test assertions.
    Debug.Assert(source != null);
    Debug.Assert(count > 0);

    // Create the helper.
    SliceHelper<T> helper = new SliceHelper<T>(
        source, count);

    // Return the helper concatenated with the skipped
    // items.
    return helper.Concat(helper.SkippedItems);
}
casperOne
A: 
List<int> list1;

List<int> list2 = new List<int>(list1);

or you can

list2.AddRange(list1);

To get a distinct list using LINQ

List<int> distinceList = list2.Distinct<int>().ToList<int>();
David Basarab
I am not looking for a distinct list. The numbers I showed in my example are distinct as a side-effect of using python's range() which gives incrementing (or decrementing) numbers.
hughdbrown
+6  A: 

The closest thing in C# would be to use the Enumerable.Skip and Enumerable.Take extension methods. You could use these to build your new list.

Reed Copsey
Upvoted for links.
user9876
+19  A: 
var newlist = oldlist.Skip(1).Concat(oldlist.Take(1));
Joel Coehoorn
Should probably add .ToList() if you want it to be equivalent.
Reed Copsey
I like. Upvoting all answers that are similar to this.
hughdbrown
Calling .ToList() before you need to is very bad for performance -- it's not something I want to encourage by leaving lying around in example code.
Joel Coehoorn
What type is newlist before the .ToList(). That var isn't helping readability :).
pbh101
IEnumerable<whateverTypeTheListHeld>
Joel Coehoorn