Jak mogę uzyskać każdy n-ty element z List <T>?

115

Używam .NET 3.5 i chciałbym mieć możliwość uzyskania każdego * n* tego elementu z listy. Nie przejmuję się tym, czy osiąga się to za pomocą wyrażenia lambda lub LINQ.

Edytować

Wygląda na to, że to pytanie wywołało sporo dyskusji (co jest dobre, prawda?). Najważniejsze, czego się nauczyłem, to to, że kiedy myślisz, że znasz każdy sposób zrobienia czegoś (nawet tak prostego), pomyśl jeszcze raz!

Paul Suart
źródło
Nie zredagowałem żadnego znaczenia twojego pierwotnego pytania; Tylko wyczyściłem to i poprawnie użyłem wielkich liter i znaków interpunkcyjnych. (.NET są kapitalizowane, LINQ jest w all-Caps, a to nie jest 'lambda', to 'wyrażenie lambda').
George Stocker
1
Zastąpiłeś „fussed” przez „pewny”, które wcale nie są synonimami.
mqp
Tak by się wydawało. Mając na pewno nie ma sensu, albo, jeżeli nie jest to „Nie jestem pewien, czy jest to możliwe do osiągnięcia za pomocą ...”
Samuel
Tak, jak rozumiem, to prawda.
mqp
fussed prawdopodobnie najlepiej byłoby zastąpić wyrażeniem „zaniepokojony”, tak aby brzmiało „Nie obchodzi mnie, czy osiągnięto to za pomocą wyrażenia lambda czy LINQ”.
TheTXI

Odpowiedzi:

191
return list.Where((x, i) => i % nStep == 0);
mqp
źródło
5
@mquander: Zauważ, że to faktycznie da ci n-ty - 1 element. Jeśli chcesz mieć rzeczywisty n-ty element (pomijając pierwszy), będziesz musiał dodać 1 do i.
casperOne,
2
Tak, przypuszczam, że zależy to w pewnym stopniu od tego, co masz na myśli, mówiąc „n-ty”, ale twoja interpretacja może być bardziej powszechna. Dodaj lub odejmij od i w zależności od potrzeb.
mqp
5
Uwaga: rozwiązanie Linq / Lambda będzie znacznie mniej wydajne niż prosta pętla ze stałym przyrostem.
MartinStettner,
5
Niekoniecznie, z odroczonym wykonaniem może być użyty w pętli foreach i tylko raz pętli oryginalnej listy.
Samuel,
1
To zależy, co masz na myśli mówiąc „praktyczny”. Jeśli potrzebujesz szybkiego sposobu na uzyskanie każdej innej pozycji z listy 30 pozycji, gdy użytkownik kliknie przycisk, powiedziałbym, że jest to równie praktyczne. Czasami wydajność nie ma już znaczenia. Oczywiście czasami tak.
mqp
37

Wiem, że to „stara szkoła”, ale dlaczego nie użyć po prostu pętli for z stepping = n?

Michael Todd
źródło
To była w zasadzie moja myśl.
Mark Pim,
1
@Michael Todd: To działa, ale problem polega na tym, że musisz wszędzie powielać tę funkcjonalność. Używając LINQ, staje się częścią złożonego zapytania.
casperOne,
8
@casperOne: Myślę, że programiści wymyślili coś, co nazywa się podprogramami, aby sobie z tym poradzić;) W prawdziwym programie prawdopodobnie użyłbym pętli, pomimo sprytnej wersji LINQ, ponieważ pętla oznacza, że ​​nie musisz iterować po każdym elemencie (
zwiększ
Zgadzam się na rozwiązanie ze starej szkoły, a nawet przypuszczam, że będzie to działać lepiej.
Jesper Fyhr Knudsen
Łatwo dać się ponieść fantazyjnej nowej składni. Ale fajnie.
Ronnie
34

Brzmi jak

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

załatwi sprawę. Nie widzę potrzeby używania Linq lub wyrażeń lambda.

EDYTOWAĆ:

Zrób to

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

i piszesz w sposób LINQish

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

2. edycja :

Aby było jeszcze bardziej LINQish

from var i in Range(0, ((myList.Length-1)/n)+1) select list[n*i];
MartinStettner
źródło
2
Podoba mi się ta metoda korzystania z metody pobierającej this [] zamiast metody Where (), która zasadniczo iteruje każdy element IEnumerable. Jeśli masz typ IList / ICollection, jest to lepsze podejście, IMHO.
spoulson,
Nie wiesz, jak działa lista, ale dlaczego używasz pętli i list[i]zamiast tego po prostu zwracasz list[n-1]?
Juan Carlos Oropeza,
@JuanCarlosOropeza zwraca każdy n-ty element (np. 0, 3, 6 ...), a nie tylko n-ty element listy.
alfoks
27

Możesz użyć przeciążenia Where, które przekazuje indeks wraz z elementem

var everyFourth = list.Where((x,i) => i % 4 == 0);
JaredPar
źródło
1
Muszę powiedzieć, że jestem fanem tej metody.
Quintin Robinson,
1
Ciągle zapominam, że możesz to zrobić - bardzo fajnie.
Stephen Newman,
10

Dla pętli

for(int i = 0; i < list.Count; i += n)
    //Nth Item..
Quintin Robinson
źródło
Count oceni wyliczalne. jeśli zostało to zrobione w sposób przyjazny linq, możesz leniwie oszacować i wziąć pierwszą wartość 100, np. `` source.TakeEvery (5) .Take (100) '' Jeśli podstawowe źródło było drogie do oszacowania, twoje podejście spowodowałoby każde element do oceny
RhysC,
1
@RhysC Dobra uwaga, ogólnie do wyliczeń. OTOH, Pytanie zostało określone List<T>, więc Countjest zdefiniowane jako tanie.
ToolmakerSteve
3

Nie jestem pewien, czy można to zrobić za pomocą wyrażenia LINQ, ale wiem, że można to zrobić za pomocą Wheremetody rozszerzenia. Na przykład, aby uzyskać co piąty przedmiot:

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

To dostanie pierwszy przedmiot, a stamtąd co piąty. Jeśli chcesz zacząć od piątej pozycji zamiast pierwszej, porównujesz z 4 zamiast z 0.

Guffa
źródło
3

Myślę, że jeśli podasz rozszerzenie linq, powinieneś być w stanie operować na najmniej specyficznym interfejsie, a więc na IEnumerable. Oczywiście, jeśli zależy ci na szybkości, szczególnie w przypadku dużego N, możesz zapewnić przeciążenie dostępu indeksowanego. Ta ostatnia eliminuje potrzebę iteracji po dużych ilościach niepotrzebnych danych i będzie znacznie szybsza niż klauzula Where. Zapewnienie obu przeciążeń umożliwia kompilatorowi wybranie najbardziej odpowiedniego wariantu.

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
źródło
To działa dla każdej listy? ponieważ próbuję użyć listy dla klasy niestandardowej i zwrócić IEnumarted <class> zamiast <class> i wymuszanie coversion (klasa) List.GetNth (1) też nie działa.
Juan Carlos Oropeza
To moja wina, że ​​muszę dołączyć 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();
}

wynik

wprowadź opis obrazu tutaj

Anwar Ul-Haq
źródło
0

Imho, żadna odpowiedź nie jest dobra. Wszystkie rozwiązania zaczynają się od 0. Ale ja chcę mieć rzeczywisty n-ty element

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
źródło
0

@belucha Podoba mi się to, ponieważ kod klienta jest bardzo czytelny, a kompilator wybiera najbardziej wydajną implementację. Oparłbym się na tym, zmniejszając wymagania IReadOnlyList<T>i oszczędzając Division dla wysokowydajnego LINQ:

    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
źródło