簡體   English   中英

C# 等效於使用 python 切片操作旋轉列表

[英]C# equivalent of rotating a list using python slice operation

在 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# 列表的兩個切片組成的新列表的等效方法是什么? 我知道如有必要,我可以通過蠻力生成。

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

您可以輕松地使用 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 的值進行切片,那么它需要 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);
}

在 C# 中最接近的是使用Enumerable.SkipEnumerable.Take擴展方法。 您可以使用這些來構建您的新列表。

List<int> list1;

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

或者你可以

list2.AddRange(list1);

使用 LINQ 獲取不同的列表

List<int> distinceList = list2.Distinct<int>().ToList<int>();

要旋轉數組,請執行a.Slice(1, null).Concat(a.Slice(null, 1))

這是我的嘗試。 a.Slice(step: -1)給出一個反向副本a[::-1]

/// <summary>
/// Slice an array as Python.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="array"></param>
/// <param name="start">start index.</param>
/// <param name="end">end index.</param>
/// <param name="step">step</param>
/// <returns></returns>
/// <remarks>
/// http://docs.python.org/2/tutorial/introduction.html#strings
///      +---+---+---+---+---+
///      | H | e | l | p | A |
///      +---+---+---+---+---+
///      0   1   2   3   4   5
/// -6  -5  -4  -3  -2  -1    
/// </remarks>
public static IEnumerable<T> Slice<T>(this T[] array,
    int? start = null, int? end = null, int step = 1)
{
    array.NullArgumentCheck("array");
    // step
    if (step == 0)
    {
        // handle gracefully
        yield break;
    }
    // step > 0
    int _start = 0;
    int _end = array.Length;
    // step < 0
    if (step < 0)
    {
        _start = -1;
        _end = -array.Length - 1;
    }
    // inputs
    _start = start ?? _start;
    _end = end ?? _end;
    // get positive index for given index
    Func<int, int, int> toPositiveIndex = (int index, int length) =>
    {
        return index >= 0 ? index : index + length;
    };
    // start
    if (_start < -array.Length || _start >= array.Length)
    {
        yield break;
    }
    _start = toPositiveIndex(_start, array.Length);
    // end
    if (_end < -array.Length - 1)
    {
        yield break;
    }
    if (_end > array.Length)
    {
        _end = array.Length;
    }
    _end = toPositiveIndex(_end, array.Length);
    // slice
    if (step > 0)
    {
        // start, end
        if (_start > _end)
        {
            yield break;
        }
        for (int i = _start; i < _end; i += step)
        {
            yield return array[i];
        }
    }
    else
    {
        // start, end
        if (_end > _start)
        {
            yield break;
        }
        for (int i = _start; i > _end; i += step)
        {
            yield return array[i];
        }
    }
}

單元測試:

[Test]
// normal cases
[TestCase(3, 5, 1, 3, 4)]
[TestCase(0, 5, 1, 0, 4)]
[TestCase(3, null, 1, 3, 9)]
[TestCase(0, null, 1, 0, 9)]
[TestCase(null, null, 1, 0, 9)]
[TestCase(0, 10, 1, 0, 9)]
[TestCase(0, int.MaxValue, 1, 0, 9)]
[TestCase(-1, null, 1, 9, 9)]
[TestCase(-2, null, 1, 8, 9)]
[TestCase(0, -2, 1, 0, 7)]
// corner cases
[TestCase(0, 0, 1, null, null)]
[TestCase(3, 5, 2, 3, 3)]
[TestCase(3, 6, 2, 3, 5)]
[TestCase(100, int.MaxValue, 1, null, null)]
[TestCase(int.MaxValue, 1, 1, null, null)]
[TestCase(-11, int.MaxValue, 1, null, null)]
[TestCase(-6, -5, 1, 4, 4)]
[TestCase(-5, -6, 1, null, null)]
[TestCase(-5, -5, 1, null, null)]
[TestCase(0, -10, 1, null, null)]
[TestCase(0, -11, 1, null, null)]
[TestCase(null, null, 100, 0, 0)]
// -ve step
[TestCase(null, null, -1, 9, 0)]
[TestCase(-7, -5, -1, null, null)]
[TestCase(-5, -7, -1, 5, 4)]
[TestCase(-5, -7, -2, 5, 5)]
[TestCase(-7, null, -1, 3, 0)]
public void Slice01(int? s, int? e, int i, int? first, int? last)
{
    var a = new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    var slice = a.Slice(start: s, end: e, step: i).ToArray();
    Print(slice);
    if (first.HasValue)
    {
        Assert.AreEqual(first, slice.First());
    }
    if (last.HasValue)
    {
        Assert.AreEqual(last, slice.Last());
    }
}

功能

/*
   list      : the list that is to be rotated
   shift     : the number of elements to be shifted
   direction : direction of shift (1 -> left, -1 -> right)/default left
*/
public List<T> Rotate<T>(List<T> list, int shift, int direction = 1)
{
    int j = 0;
    List<T> temp_buffer = new List<T>();

    if(direction == -1) 
    {
        shift = list.Count - shift; 
    }

    for (int i = 0; i < list.Count; i++)
    {
        if (i < shift)
        {
            temp_buffer.Add(list[i]);
        }

        if (i < list.Count - shift)
        {
            list[i] = list[i + shift];
        }
        else
        {
            list[i] = temp_buffer[j];
            j++;
        }
    }
    return list;
}

使用示例

List<int> list = new List<int>() {1, 2, 3, 4, 5, 6, 7, 8, 9};
int shift = 4;  //enter the number of elements to be shifted

//printing the list before rotating
Console.Write("Before\t: ");
for(int i = 0; i< list.Count; i++)
{
    Console.Write(list[i] + " ");   
}

list = Rotate<int>(list, shift, -1);

//printing the list after rotating
Console.Write("\nAfter \t: ");
for(int i = 0; i< list.Count; i++)
{
    Console.Write(list[i] + " ");   
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM