Wie kann ich jedes n-te Element aus einer Liste <T> abrufen?


114

Ich verwende .NET 3.5 und möchte in der Lage sein, jedes * n* -te Element aus einer Liste abzurufen. Es stört mich nicht, ob dies mit einem Lambda-Ausdruck oder LINQ erreicht wird.

Bearbeiten

Sieht so aus, als hätte diese Frage eine Menge Debatten ausgelöst (was gut ist, oder?). Die Hauptsache, die ich gelernt habe, ist, dass Sie, wenn Sie glauben, jeden Weg zu kennen (auch so einfach), noch einmal darüber nachdenken!


Ich habe keine Bedeutung hinter Ihrer ursprünglichen Frage herausgearbeitet. Ich habe es nur aufgeräumt und Groß- und Kleinschreibung und Zeichensetzung richtig verwendet. (.NET wird groß geschrieben, LINQ ist in All-Caps und es ist kein "Lambda", es ist ein "Lambda-Ausdruck".)
George Stocker

1
Sie haben "aufgeregt" durch "sicher" ersetzt, die überhaupt keine Synonyme sind.
mqp

Würde so scheinen. Sicher zu sein macht auch keinen Sinn, es sei denn, es ist "Ich bin nicht sicher, ob es mit ... erreichbar ist"
Samuel

Ja, so wie ich es verstehe, ist das ungefähr richtig.
mqp

Aufgeregt wäre es wahrscheinlich am besten, durch "besorgt" ersetzt zu werden, so dass "Ich bin nicht besorgt darüber, ob es mit einem Lambda-Ausdruck oder LINQ erreicht wird."
TheTXI

Antworten:


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

5
@mquander: Beachten Sie, dass dies tatsächlich das n-te 1-Element ergibt. Wenn Sie die tatsächlichen n-ten Elemente möchten (das erste überspringen), müssen Sie 1 zu i hinzufügen.
casperOne

2
Ja, ich nehme an, es hängt irgendwie davon ab, was Sie mit "n-ten" meinen, aber Ihre Interpretation könnte häufiger sein. Addiere oder subtrahiere von i, um deinen Bedürfnissen zu entsprechen.
mqp

5
Nur zur Anmerkung: Die Linq / Lambda-Lösung ist viel weniger leistungsfähig als eine einfache Schleife mit festem Inkrement.
MartinStettner

5
Nicht unbedingt, bei verzögerter Ausführung könnte es in einer foreach-Schleife verwendet werden und die ursprüngliche Liste nur einmal durchlaufen.
Samuel

1
Es kommt darauf an, was Sie unter "praktisch" verstehen. Wenn Sie einen schnellen Weg benötigen, um jedes andere Element in eine Liste von 30 Elementen aufzunehmen, wenn der Benutzer auf eine Schaltfläche klickt, würde ich sagen, dass dies genauso praktisch ist. Manchmal spielt Leistung wirklich keine Rolle mehr. Natürlich manchmal.
mqp

37

Ich weiß, dass es "alte Schule" ist, aber warum nicht einfach eine for-Schleife mit steping = n verwenden?


Das war im Grunde mein Gedanke.
Mark Pim

1
@ Michael Todd: Es funktioniert, aber das Problem dabei ist, dass Sie diese Funktionalität überall duplizieren müssen. Durch die Verwendung von LINQ wird es Teil der zusammengesetzten Abfrage.
casperOne

8
@casperOne: Ich glaube, Programmierer haben dieses Ding namens Subroutinen erfunden, um damit umzugehen;) In einem echten Programm würde ich wahrscheinlich trotz der cleveren LINQ-Version eine Schleife verwenden, da eine Schleife bedeutet, dass Sie nicht über jedes Element iterieren müssen (
Erhöhen Sie

Ich bin damit einverstanden, mich für die Old-School-Lösung zu entscheiden, und ich vermute sogar, dass dies eine bessere Leistung bringen wird.
Jesper Fyhr Knudsen

Einfach mit der ausgefallenen neuen Syntax mitzureißen. Es macht aber Spaß.
Ronnie

34

Hört sich an wie

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

würde den Trick machen. Ich sehe keine Notwendigkeit, Linq- oder Lambda-Ausdrücke zu verwenden.

BEARBEITEN:

Mach es

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

und Sie schreiben auf LINQish-Weise

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

2. Bearbeitung :

Um es noch LINQish zu machen

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

2
Ich mag diese Methode, um den this [] -Getter anstelle der Where () -Methode zu verwenden, die im Wesentlichen jedes Element der IEnumerable iteriert. Wenn Sie einen IList / ICollection-Typ haben, ist dies meiner Meinung nach der bessere Ansatz.
Spoulson

Sie sind sich nicht sicher, wie die Liste funktioniert, aber warum verwenden Sie eine Schleife und kehren list[i]stattdessen einfach zurück list[n-1]?
Juan Carlos Oropeza

@JuanCarlosOropeza gibt er jedes n-te Element zurück (zB 0, 3, 6 ...), nicht nur das n-te Element der Liste.
Alfoks

27

Sie können die Where-Überladung verwenden, die den Index zusammen mit dem Element übergibt

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

1
Ich muss sagen, ich bin ein Fan dieser Methode.
Quintin Robinson

1
Ich vergesse immer wieder, dass du das kannst - sehr schön.
Stephen Newman

10

Für Schleife

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

Count wertet die Aufzählung aus. Wenn dies auf linq-freundliche Weise durchgeführt würde, könnten Sie träge auswerten und den ersten 100-Wert nehmen, z. B. `` source.TakeEvery (5) .Take (100) `` Wenn die zugrunde liegende Quelle teuer zu bewerten wäre, würde Ihr Ansatz jeden verursachen zu bewertendes Element
RhysC

1
@ RhysC Guter Punkt für Aufzählungen im Allgemeinen. OTOH, Frage hat angegeben List<T>, Countist also als billig definiert.
ToolmakerSteve

3

Ich bin nicht sicher, ob es mit einem LINQ-Ausdruck möglich ist, aber ich weiß, dass Sie die WhereErweiterungsmethode verwenden können, um dies zu tun. Zum Beispiel, um jeden fünften Artikel zu erhalten:

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

Dies wird den ersten Gegenstand und jeden fünften von dort erhalten. Wenn Sie mit dem fünften Element anstelle des ersten beginnen möchten, vergleichen Sie mit 4 anstatt mit 0.


3

Ich denke, wenn Sie eine linq-Erweiterung bereitstellen, sollten Sie in der Lage sein, auf der am wenigsten spezifischen Schnittstelle zu arbeiten, also auf IEnumerable. Wenn Sie speziell für große N auf dem neuesten Stand sind, können Sie natürlich eine Überlastung für den indizierten Zugriff bereitstellen. Letzteres macht das Iterieren über große Mengen nicht benötigter Daten überflüssig und ist viel schneller als die Where-Klausel. Wenn Sie beide Überladungen bereitstellen, kann der Compiler die am besten geeignete Variante auswählen.

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

Dies funktioniert für jede Liste? weil ich versuche, in einer Liste für eine benutzerdefinierte Klasse zu verwenden und eine IEnumarted <Klasse> anstelle von <Klasse> zurückzugeben und die Coversion (Klasse) List.GetNth (1) zu erzwingen funktioniert auch nicht.
Juan Carlos Oropeza

War meine Schuld, muss ich GetNth (1) .FirstOrDefault () einschließen;
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();
}

Ausgabe

Geben Sie hier die Bildbeschreibung ein


0

Imho keine Antwort ist richtig. Alle Lösungen beginnen bei 0. Aber ich möchte das echte n-te Element haben

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 Das gefällt mir, weil der Client-Code sehr gut lesbar ist und der Compiler die effizienteste Implementierung auswählt. Ich würde darauf aufbauen, indem ich die Anforderungen auf IReadOnlyList<T>die Division für Hochleistungs-LINQ reduziere und diese speichere:

    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];
        }
    }
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.