Comment puis-je obtenir chaque nième élément d'une liste <T>?


115

J'utilise .NET 3.5 et j'aimerais pouvoir obtenir tous les *n * ème élément d'une liste. Je ne suis pas dérangé de savoir si cela est réalisé à l'aide d'une expression lambda ou de LINQ.

Éditer

On dirait que cette question a provoqué beaucoup de débats (ce qui est une bonne chose, non?). La principale chose que j'ai apprise est que lorsque vous pensez connaître toutes les façons de faire quelque chose (même aussi simple que cela), détrompez-vous!


Je n'ai supprimé aucune signification derrière votre question initiale; Je l'ai seulement nettoyé et utilisé correctement les majuscules et la ponctuation. (.NET est en majuscule, LINQ est en majuscules, et ce n'est pas un `` lambda '', c'est une `` expression lambda ''.)
George Stocker

1
Vous avez remplacé «fussed» par «sure» qui ne sont pas du tout des synonymes.
mqp

Cela le semblerait. Avoir la certitude n'a pas de sens non plus, à moins que ce ne soit "Je ne suis pas sûr que ce soit réalisable en utilisant ..."
Samuel

Oui, si je comprends bien, c'est à peu près correct.
mqp

tracassé serait probablement préférable d'être remplacé par "concerné" afin qu'il se lit "Je ne suis pas préoccupé de savoir si c'est réalisé en utilisant une expression lambda ou LINQ."
TheTXI

Réponses:


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

5
@mquander: Notez que cela vous donnera en fait le nième - 1 élément. Si vous voulez les nièmes éléments réels (en sautant le premier), vous devrez ajouter 1 à i.
casperOne

2
Oui, je suppose que cela dépend en quelque sorte de ce que vous entendez par «nième», mais votre interprétation pourrait être plus courante. Ajoutez ou soustrayez de i en fonction de vos besoins.
mqp

5
A noter: la solution Linq / Lambda sera bien moins performante qu'une simple boucle à incrément fixe.
MartinStettner

5
Pas nécessairement, avec une exécution différée, il pourrait être utilisé dans une boucle foreach et ne boucle qu'une seule fois sur la liste d'origine.
Samuel

1
Cela dépend de ce que vous entendez par «pratique». Si vous avez besoin d'un moyen rapide d'obtenir tous les autres éléments d'une liste de 30 éléments lorsque l'utilisateur clique sur un bouton, je dirais que c'est tout aussi pratique. Parfois, la performance n'a plus vraiment d'importance. Bien sûr, parfois c'est le cas.
mqp

37

Je sais que c'est "old school", mais pourquoi ne pas utiliser simplement une boucle for avec pas à pas = n?


C'était essentiellement ma pensée.
Mark Pim

1
@Michael Todd: Cela fonctionne, mais le problème avec cela est que vous devez dupliquer cette fonctionnalité partout. En utilisant LINQ, il devient partie de la requête composée.
casperOne

8
@casperOne: Je crois que les programmeurs ont inventé cette chose appelée sous-programmes pour gérer cela;) Dans un vrai programme, j'utiliserais probablement une boucle, malgré la version intelligente de LINQ, car une boucle signifie que vous n'avez pas à itérer sur chaque élément ( incrémenter l'index de N.)
mqp

Je suis d'accord pour la solution de la vieille école, et je suppose même que cela fonctionnera mieux.
Jesper Fyhr Knudsen

Facile à emporter avec une nouvelle syntaxe sophistiquée. C'est amusant cependant.
Ronnie

34

Sonne comme

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

ferait l'affaire. Je ne vois pas la nécessité d'utiliser des expressions Linq ou une expression lambda.

ÉDITER:

Fais-le

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

et vous écrivez de manière LINQish

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

2e édition :

Pour le rendre encore plus LINQish

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

2
J'aime cette méthode pour utiliser le getter this [] au lieu de la méthode Where (), qui itère essentiellement chaque élément de IEnumerable. Si vous avez un type IList / ICollection, c'est la meilleure approche, à mon humble avis.
spoulson

Vous ne savez pas comment fonctionne la liste, mais pourquoi vous utilisez une boucle et retournez list[i]simplement revenir list[n-1]?
Juan Carlos Oropeza

@JuanCarlosOropeza il renvoie chaque nième élément (par exemple 0, 3, 6 ...), pas seulement le nième élément de la liste.
alfoks

27

Vous pouvez utiliser la surcharge Where qui transmet l'index avec l'élément

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

1
Je dois dire que je suis fan de cette méthode.
Quintin Robinson

1
J'oublie toujours que tu peux faire ça - très gentil.
Stephen Newman

10

Pour la boucle

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

Count évaluera l'énumérable. si cela était fait d'une manière linq amicale, alors vous pourriez paresseusement évaluer et prendre la première valeur 100, par exemple `` source.TakeEvery (5) .Take (100) `` Si la source sous-jacente était coûteuse à évaluer, votre approche entraînerait chaque élément à évaluer
RhysC

1
@RhysC Bon point, pour les énumérables en général. OTOH, Question a spécifié List<T>, donc Countest défini comme bon marché.
ToolmakerSteve

3

Je ne sais pas s'il est possible de faire avec une expression LINQ, mais je sais que vous pouvez utiliser la Whereméthode d'extension pour le faire. Par exemple, pour obtenir un article sur cinq:

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

Cela obtiendra le premier élément et tous les cinq à partir de là. Si vous voulez commencer au cinquième élément au lieu du premier, vous comparez avec 4 au lieu de comparer avec 0.


3

Je pense que si vous fournissez une extension linq, vous devriez pouvoir opérer sur l'interface la moins spécifique, donc sur IEnumerable. Bien sûr, si vous êtes prêt pour la vitesse, en particulier pour les grands N, vous pourriez fournir une surcharge pour l'accès indexé. Cette dernière supprime la nécessité d'itérer sur de grandes quantités de données inutiles et sera beaucoup plus rapide que la clause Where. Fournir les deux surcharges permet au compilateur de sélectionner la variante la plus appropriée.

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

Cela fonctionne pour n'importe quelle liste? parce que j'essaie d'utiliser dans une liste pour une classe personnalisée et de renvoyer une <classe> IEnumarted au lieu de <class> et forcer la conversion (classe) List.GetNth (1) ne fonctionne pas non plus.
Juan Carlos Oropeza

Était ma faute, je dois inclure 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();
}

production

entrez la description de l'image ici


0

Imho aucune réponse n'est juste. Toutes les solutions commencent à 0. Mais je veux avoir le nième élément réel

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 J'aime ça, car le code client est très lisible et le compilateur choisit l'implémentation la plus efficace. Je tirerais parti de cela en réduisant les exigences IReadOnlyList<T>et en sauvegardant la Division pour LINQ haute performance:

    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];
        }
    }
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.