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!

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

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

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

Anwar Ul Haq
quelle
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];
}
user2340145
quelle
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];
        }
    }
Spoc
quelle