C#中如何使用切片操作旋转列表

37 投票
6 回答
7614 浏览
提问于 2025-04-15 13:43

在Python中,我可以把一个列表my_list的内容旋转一下:

>>> 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]

那么在C#中,有什么方法可以创建一个新的列表,这个新列表是由一个已有的C#列表的两个部分组合而成的呢?我知道如果需要的话可以用比较笨的方法来实现。

6 个回答

9

在C#中,最接近的做法就是使用 Enumerable.SkipEnumerable.Take 这两个扩展方法。你可以用它们来创建你新的列表。

18

你可以很简单地用LINQ来做到这一点:

// 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));

你甚至可以把这个作为一个扩展方法来添加,像这样:

public static IEnumerable<T> Slice<T>(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));
}

当然,上面的代码需要一些错误检查,但大体思路就是这样。


再想想,这个算法还有很多可以改进的地方,这样可以提高性能。

如果你的 IEnumerable<T> 实例实现了 IList<T> 或者是一个数组,那就可以充分利用它是有索引的这个特点。

另外,你还可以减少在消息主体中需要跳过和获取的迭代次数。

举个例子,如果你有200个项目,而你想要切片到199,那么在Slice方法的主体中需要进行199次(初始跳过) + 1次(剩下的项目) + 199次(获取)的迭代。这可以通过只遍历一次列表来减少,遍历时把项目存储在一个列表中,然后将这个列表与自己连接(这样就不需要再迭代了)。

在这种情况下,权衡的就是内存使用。

为此,我建议扩展方法可以这样写:

public static IEnumerable<T> Slice<T>(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>(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<T>(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);
}
41

在编程中,有时候我们会遇到一些问题,可能是代码运行不正常,或者是我们不明白某个功能是怎么工作的。这种情况下,很多人会选择去StackOverflow这个网站寻求帮助。这个网站就像一个大型的问答社区,程序员们可以在这里提问,也可以回答别人的问题。

在提问时,最好把问题描述得清楚一些,比如你遇到的具体情况、你使用的代码,以及你希望得到什么样的帮助。这样,其他人才能更好地理解你的问题,并给出有效的建议。

同时,查看别人提问和回答的问题也是一个很好的学习方式。通过阅读这些内容,你可以了解到很多编程的技巧和解决方案,这对提高自己的编程能力非常有帮助。

总之,StackOverflow是一个很有用的资源,适合所有想要学习编程的人。无论你是新手还是有经验的程序员,都能在这里找到有价值的信息。

var newlist = oldlist.Skip(1).Concat(oldlist.Take(1));

撰写回答