2009-08-19 11 views
34

in Python, posso prendere un elenco my_list e ruotare i contenuti:C# equivalente di rotazione di una lista usando fetta python operazione

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

Qual è il modo equivalente in C# per creare un nuovo elenco che è un composto di due sezioni di un elenco C# esistente? So che posso generare con la forza bruta, se necessario.

risposta

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

Probabilmente dovrebbe aggiungere .ToList() se vuoi che sia equivalente. –

+11

Chiamare .ToList() prima che sia necessario è molto brutto per le prestazioni - non è qualcosa che voglio incoraggiare lasciando in giro nel codice di esempio. –

+1

Che tipo è newlist prima di .ToList(). Quella var non aiuta la leggibilità :). – pbh101

18

Si può facilmente utilizzare LINQ per fare questo:

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

Si potrebbe anche aggiungere questo come un metodo di estensione in questo modo:

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

Naturalmente ci deve essere qualche controllo degli errori nel sopra, ma questa è la premessa generale.


Pensandoci sopra, ci sono miglioramenti definitivi che possono essere apportati a questo algoritmo che migliorerebbe le prestazioni.

Si può sicuramente approfittare se l'istanza IEnumerable<T> implementa IList<T> o è un array, sfruttando il fatto che è indicizzato.

Inoltre, è possibile ridurre il numero di iterazioni necessarie per saltare e prendere nel corpo del messaggio.

Ad esempio, se si dispone di 200 elementi e si desidera tagliare con un valore di 199, quindi richiede 199 (per il salto iniziale) + 1 (per il resto dell'articolo) + 199 (per il Take) iterazioni in il corpo del metodo Slice. Questo può essere ridotto ripetendo l'elenco una volta, memorizzando gli elementi in un elenco che viene poi concatenato a se stesso (non richiede alcuna iterazione).

In questo caso, lo scambio qui è memoria.

A tal fine, propongo quanto segue per il metodo di estensione:

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); 
} 
1
List<int> list1; 

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

oppure si può

list2.AddRange(list1); 

Per avere una lista distinta utilizzando LINQ

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

Non sto cercando per una lista distinta.I numeri che ho mostrato nel mio esempio sono distinti come un effetto collaterale dell'uso di python's range() che dà numeri di incremento (o decremento). – hughdbrown

9

La cosa più vicina in C# sarebbe usare lo Enumerable.Skip e metodi di estensione Enumerable.Take. Potresti usarli per costruire il tuo nuovo elenco.

1

Per ruotare array, fare a.Slice(1, null).Concat(a.Slice(null, 1)).

Ecco la mia pugnalata. a.Slice(step: -1) restituisce una copia invertita come 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 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()); 
    } 
} 
Problemi correlati