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!

Paul Suart
la source
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);
mqp
la source
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?

Michael Todd
la source
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];
MartinStettner
la source
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);
JaredPar
la source
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..
Quintin Robinson
la source
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.

Guffa
la source
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];
    }
}
Belucha
la source
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

Anwar Ul Haq
la source
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];
}
utilisateur2340145
la source
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];
        }
    }
Spoc
la source