36 votes

Équivalent C# de la rotation d'une liste à l'aide de l'opération slice de python

En python, je peux prendre une liste my_list et faire tourner son contenu :

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

Quelle est la méthode équivalente en C# pour créer une nouvelle liste composée de deux tranches d'une liste C# existante ? Je sais que je peux générer par force brute si nécessaire.

41voto

Joel Coehoorn Points 190579
var newlist = oldlist.Skip(1).Concat(oldlist.Take(1));

0 votes

Il faudrait probablement ajouter .ToList() si vous voulez que ce soit équivalent.

11 votes

Appeler .ToList() avant d'en avoir besoin est très mauvais pour les performances. Ce n'est pas quelque chose que je veux encourager en le laissant traîner dans le code d'exemple.

1 votes

Quel est le type de newlist avant le .ToList(). Cette var n'aide pas à la lisibilité :).

18voto

casperOne Points 49736

Vous pouvez facilement utiliser LINQ pour ce faire :

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

Vous pourriez même l'ajouter en tant que méthode d'extension comme ceci :

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

Bien sûr, il est nécessaire de vérifier les erreurs dans ce qui précède, mais c'est le principe général.


En y réfléchissant davantage, il est possible d'apporter des améliorations à cet algorithme, ce qui permettrait d'améliorer les performances.

Vous pouvez certainement profiter de la IEnumerable<T> instance implémente IList<T> ou est un tableau, en profitant du fait qu'il est indexé.

Vous pouvez également réduire le nombre d'itérations nécessaires pour sauter et prendre dans le corps du message.

Par exemple, si vous avez 200 éléments et que vous voulez les couper avec une valeur de 199, il faut 199 (pour le saut initial) + 1 (pour l'élément restant) + 199 (pour la prise) itérations dans le corps de la méthode Slice. Cela peut être réduit en itérant une fois dans la liste, en stockant les éléments dans une liste qui est ensuite concaténée à elle-même (ne nécessitant aucune itération).

Dans ce cas, la contrepartie est la mémoire.

À cette fin, je propose ce qui suit pour la méthode d'extension :

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

9voto

Reed Copsey Points 315315

La solution la plus proche en C# serait d'utiliser la fonction Enumerable.Skip y Enumerable.Take les méthodes d'extension. Vous pourriez les utiliser pour construire votre nouvelle liste.

1voto

David Basarab Points 25852
List<int> list1;

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

ou vous pouvez

list2.AddRange(list1);

Pour obtenir une liste distincte en utilisant LINQ

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

0 votes

Je ne cherche pas à établir une liste distincte. Les nombres que j'ai montrés dans mon exemple sont distincts comme un effet secondaire de l'utilisation de range() de Python qui donne des nombres incrémentaux (ou décrémentaux).

1voto

hIpPy Points 727

Pour faire pivoter le tableau, faites a.Slice(1, null).Concat(a.Slice(null, 1)) .

Voici mon coup d'essai. a.Slice(step: -1) donne une copie inversée comme 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];
        }
    }
}

des tests nunit :

[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());
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X