Come posso ottenere ogni ennesimo articolo da un List <T>?


114

Sto usando .NET 3.5 e vorrei essere in grado di ottenere ogni * n* esimo elemento da un elenco. Non mi interessa sapere se è stato ottenuto utilizzando un'espressione lambda o LINQ.

modificare

Sembra che questa domanda abbia provocato un bel po 'di dibattito (che è una buona cosa, giusto?). La cosa principale che ho imparato è che quando pensi di conoscere tutti i modi per fare qualcosa (anche semplice come questo), ripensaci!


Non ho cancellato alcun significato dietro la tua domanda originale; L'ho solo ripulito e ho usato correttamente le maiuscole e la punteggiatura. (.NET è in maiuscolo, LINQ è tutto maiuscolo e non è un "lambda", è un '"espressione lambda".)
George Stocker,

1
Hai sostituito "fussed" con "sure" che non sono affatto sinonimi.
mqp

Sembrerebbe così. Avere la certezza non ha nemmeno senso, a meno che non sia "Non sono sicuro che sia realizzabile usando ..."
Samuel

Sì, a quanto ho capito, è giusto.
mqp

fussed sarebbe probabilmente la cosa migliore da sostituire con "interessato" in modo che si legga "Non mi interessa sapere se è stato ottenuto utilizzando un'espressione lambda o LINQ".
TheTXI

Risposte:


189
return list.Where((x, i) => i % nStep == 0);

5
@mquander: nota che questo ti darà effettivamente l'ennesimo - 1 elemento. Se vuoi gli effettivi ennesimi elementi (saltando il primo), dovrai aggiungere 1 a i.
casperOne

2
Sì, suppongo che dipenda da cosa intendi per "nth", ma la tua interpretazione potrebbe essere più comune. Aggiungi o sottrai da i in base alle tue esigenze.
mqp

5
Solo per notare: la soluzione Linq / Lambda sarà molto meno performante di un semplice ciclo con incremento fisso.
MartinStettner

5
Non necessariamente, con l'esecuzione differita potrebbe essere utilizzato in un ciclo foreach e solo una volta viene eseguito il ciclo sull'elenco originale.
Samuel

1
Dipende da cosa intendi per "pratico". Se hai bisogno di un modo rapido per ottenere ogni altro elemento in un elenco di 30 elementi quando l'utente fa clic su un pulsante, direi che è altrettanto pratico. A volte le prestazioni non contano più. Certo, a volte lo fa.
mqp

37

So che è "vecchia scuola", ma perché non usare semplicemente un ciclo for con stepping = n?


Questo era fondamentalmente il mio pensiero.
Mark Pim

1
@Michael Todd: Funziona, ma il problema è che devi duplicare quella funzionalità ovunque. Utilizzando LINQ, diventa parte della query composta.
casperOne

8
@casperOne: credo che i programmatori abbiano inventato questa cosa chiamata subroutine per far fronte a questo;) In un programma reale probabilmente userei un ciclo, nonostante l'intelligente versione LINQ, poiché un ciclo significa che non devi iterare su ogni elemento ( incrementare l'indice di N.)
mqp

Sono d'accordo con la soluzione vecchia scuola, e immagino anche che funzionerà meglio.
Jesper Fyhr Knudsen

Facile lasciarsi trasportare dalla nuova sintassi stravagante. Comunque è divertente.
Ronnie

34

Sembra

IEnumerator<T> GetNth<T>(List<T> list, int n) {
  for (int i=0; i<list.Count; i+=n)
    yield return list[i]
}

farebbe il trucco. Non vedo la necessità di utilizzare Linq o espressioni lambda.

MODIFICARE:

Fallo

public static class MyListExtensions {
  public static IEnumerable<T> GetNth<T>(this List<T> list, int n) {
    for (int i=0; i<list.Count; i+=n)
      yield return list[i];
  }
}

e scrivi in ​​modo LINQish

from var element in MyList.GetNth(10) select element;

2a modifica :

Per renderlo ancora più LINQish

from var i in Range(0, ((myList.Length-1)/n)+1) select list[n*i];

2
Mi piace questo metodo per utilizzare il metodo this [] getter invece del metodo Where (), che essenzialmente itera ogni elemento di IEnumerable. Se hai un tipo IList / ICollection, questo è l'approccio migliore, IMHO.
spoulson

Non sei sicuro di come funzioni la lista, ma perché usi un ciclo e ritorni list[i]invece solo di ritorno list[n-1]?
Juan Carlos Oropeza,

@JuanCarlosOropeza restituisce ogni n-esimo elemento (es. 0, 3, 6 ...), non solo l'ennesimo elemento della lista.
alfoks

27

È possibile utilizzare l'overload Where che passa l'indice insieme all'elemento

var everyFourth = list.Where((x,i) => i % 4 == 0);

1
Devo dire che sono un fan di questo metodo.
Quintin Robinson

1
Continuo a dimenticarmi che puoi farlo - molto carino.
Stephen Newman

10

Per Loop

for(int i = 0; i < list.Count; i += n)
    //Nth Item..

Count valuterà l'enumerabile. se questo è stato fatto in un modo amichevole con linq, potresti pigramente valutare e prendere il primo valore di 100 es. `` source.TakeEvery (5) .Take (100) elemento da valutare
RhysC

1
@RhysC Buon punto, per gli enumerabili in generale. OTOH, Question ha specificato List<T>, quindi Countè definito economico.
ToolmakerSteve

3

Non sono sicuro che sia possibile farlo con un'espressione LINQ, ma so che puoi usare il Wheremetodo di estensione per farlo. Ad esempio, per ottenere ogni quinto articolo:

List<T> list = originalList.Where((t,i) => (i % 5) == 0).ToList();

Questo otterrà il primo oggetto e ogni quinto da lì. Se vuoi iniziare dal quinto elemento invece del primo, confronta con 4 invece di confrontare con 0.


3

Penso che se fornisci un'estensione per linq, dovresti essere in grado di operare sull'interfaccia meno specifica, quindi su IEnumerable. Ovviamente, se sei pronto per la velocità soprattutto per N grandi, potresti fornire un sovraccarico per l'accesso indicizzato. Quest'ultimo elimina la necessità di iterare su grandi quantità di dati non necessari e sarà molto più veloce della clausola Where. Fornire entrambi gli overload consente al compilatore di selezionare la variante più adatta.

public static class LinqExtensions
{
    public static IEnumerable<T> GetNth<T>(this IEnumerable<T> list, int n)
    {
        if (n < 0)
            throw new ArgumentOutOfRangeException("n");
        if (n > 0)
        {
            int c = 0;
            foreach (var e in list)
            {
                if (c % n == 0)
                    yield return e;
                c++;
            }
        }
    }
    public static IEnumerable<T> GetNth<T>(this IList<T> list, int n)
    {
        if (n < 0)
            throw new ArgumentOutOfRangeException("n");
        if (n > 0)
            for (int c = 0; c < list.Count; c += n)
                yield return list[c];
    }
}

Funziona per qualsiasi elenco? perché provo a utilizzare in un elenco per una classe personalizzata e restituisco un IEnumarted <class> invece di <class> e forzare coversion (class) List.GetNth (1) non funziona neanche.
Juan Carlos Oropeza

È stata colpa mia se devo includere GetNth (1) .FirstOrDefault ();
Juan Carlos Oropeza

0
private static readonly string[] sequence = "1,2,3,4,5,6,7,8,9,10,11,12,13,14,15".Split(',');

static void Main(string[] args)
{
    var every4thElement = sequence
      .Where((p, index) => index % 4 == 0);

    foreach (string p in every4thElement)
    {
        Console.WriteLine("{0}", p);
    }

    Console.ReadKey();
}

produzione

inserisci qui la descrizione dell'immagine


0

Imho nessuna risposta è giusta. Tutte le soluzioni iniziano da 0. Ma voglio avere l'ennesimo elemento reale

public static IEnumerable<T> GetNth<T>(this IList<T> list, int n)
{
    for (int i = n - 1; i < list.Count; i += n)
        yield return list[i];
}

0

@belucha Mi piace, perché il codice client è molto leggibile e il compilatore sceglie l'implementazione più efficiente. Vorrei basarmi su questo riducendo i requisiti IReadOnlyList<T>e per salvare la divisione per LINQ ad alte prestazioni:

    public static IEnumerable<T> GetNth<T>(this IEnumerable<T> list, int n) {
        if (n <= 0) throw new ArgumentOutOfRangeException(nameof(n), n, null);
        int i = n;
        foreach (var e in list) {
            if (++i < n) { //save Division
                continue;
            }
            i = 0;
            yield return e;
        }
    }

    public static IEnumerable<T> GetNth<T>(this IReadOnlyList<T> list, int n
        , int offset = 0) { //use IReadOnlyList<T>
        if (n <= 0) throw new ArgumentOutOfRangeException(nameof(n), n, null);
        for (var i = offset; i < list.Count; i += n) {
            yield return list[i];
        }
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.