Como posso obter cada enésimo item de um List <T>?

114

Estou usando o .NET 3.5 e gostaria de obter cada * n* o item de uma lista. Não estou preocupado em saber se isso é alcançado usando uma expressão lambda ou LINQ.

Editar

Parece que essa pergunta provocou bastante debate (o que é bom, certo?). A principal coisa que aprendi é que, quando você acha que sabe todas as maneiras de fazer algo (mesmo tão simples quanto isso), pense novamente!

Paul Suart
fonte
Eu não eliminei nenhum significado por trás de sua pergunta original; Eu apenas limpei e usei letras maiúsculas e pontuação corretamente. (.NET é maiúsculo, LINQ está em maiúsculas e não é um 'lambda', é uma 'expressão lambda'.)
George Stocker,
1
Você substituiu "agitado" por "certo", que não são sinônimos.
mqp
Parece que sim. Ter certeza também não faz sentido, a menos que seja "Não tenho certeza se é possível usar ..."
Samuel
Sim, pelo que entendi, está certo.
mqp
agitado provavelmente seria melhor ser substituído por "preocupado" para que se lesse "Não estou preocupado se isso foi alcançado usando uma expressão lambda ou LINQ."
TheTXI

Respostas:

189
return list.Where((x, i) => i % nStep == 0);
mqp
fonte
5
@mquander: Observe que isso realmente fornecerá o enésimo - 1 elemento. Se você quiser os enésimos elementos reais (pulando o primeiro), você terá que adicionar 1 a i.
casperOne
2
Sim, suponho que depende do que você entende por "enésimo", mas sua interpretação pode ser mais comum. Adicione ou subtraia de i para atender às suas necessidades.
mqp
5
Apenas para observar: a solução Linq / Lambda terá muito menos desempenho do que um loop simples com incremento fixo.
MartinStettner
5
Não necessariamente, com a execução adiada, ele poderia ser usado em um loop foreach e apenas fazer um loop sobre a lista original uma vez.
Samuel,
1
Depende do que você entende por "prático". Se você precisa de uma maneira rápida de obter todos os outros itens em uma lista de 30 itens quando o usuário clica em um botão, eu diria que isso é muito prático. Às vezes, o desempenho realmente não importa mais. Claro, às vezes sim.
mqp
37

Eu sei que é "old school", mas por que não usar um loop for com stepping = n?

Michael Todd
fonte
Esse foi basicamente o meu pensamento.
Mark Pim
1
@Michael Todd: Funciona, mas o problema é que você precisa duplicar essa funcionalidade em todos os lugares. Ao usar o LINQ, ele se torna parte da consulta composta.
casperOne
8
@casperOne: Eu acredito que os programadores inventaram essa coisa chamada sub-rotinas para lidar com isso;) Em um programa real, eu provavelmente usaria um loop, apesar da versão inteligente do LINQ, uma vez que um loop significa que você não precisa iterar em cada elemento ( incrementar o índice em N.)
mqp
Eu concordo em ir para a solução da velha escola, e estou até supondo que isso terá um desempenho melhor.
Jesper Fyhr Knudsen
Fácil de se deixar levar pela nova sintaxe sofisticada. É divertido embora.
Ronnie
34

Soa como

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

faria o truque. Não vejo necessidade de usar expressões Linq ou lambda.

EDITAR:

Faça

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

e você escreve de maneira LINQish

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

2ª Edição :

Para torná-lo ainda mais LINQish

from var i in Range(0, ((myList.Length-1)/n)+1) select list[n*i];
MartinStettner
fonte
2
Eu gosto desse método para usar o método this [] getter em vez do método Where (), que essencialmente itera cada elemento do IEnumerable. Se você tiver um tipo IList / ICollection, esta é a melhor abordagem, IMHO.
spoulson
Não tenho certeza de como a lista funciona, mas por que você usa um loop e retorna em list[i]vez de apenas retornar list[n-1]?
Juan Carlos Oropeza
@JuanCarlosOropeza ele retorna todo enésimo elemento (por exemplo, 0, 3, 6 ...), não apenas o enésimo elemento da lista.
alfoks
27

Você pode usar a sobrecarga de Where, que passa o índice junto com o elemento

var everyFourth = list.Where((x,i) => i % 4 == 0);
JaredPar
fonte
1
Devo dizer que sou fã desse método.
Quintin Robinson
1
Sempre esqueço que você pode fazer isso - muito bom.
Stephen Newman,
10

For Loop

for(int i = 0; i < list.Count; i += n)
    //Nth Item..
Quintin Robinson
fonte
A contagem avaliará o enumerável. se isso fosse feito de maneira amigável ao linq, então você poderia preguiçosamente avaliar e pegar os primeiros 100 valores, por exemplo, `` source.TakeEvery (5) .Take (100) `` Se a fonte subjacente fosse cara para avaliar, então sua abordagem causaria todos elemento a ser avaliado
RhysC
1
@RhysC Bom ponto, para enumeráveis ​​em geral. OTOH, Question especificou List<T>, então Counté definido como barato.
Toolmaker Steve
3

Não tenho certeza se é possível fazer com uma expressão LINQ, mas sei que você pode usar o Wheremétodo de extensão para fazer isso. Por exemplo, para obter cada quinto item:

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

Isso obterá o primeiro item e a cada cinco a partir daí. Se você quiser começar no quinto item em vez do primeiro, compare com 4 em vez de comparar com 0.

Guffa
fonte
3

Eu acho que se você fornecer uma extensão linq, deverá ser capaz de operar na interface menos específica, portanto, em IEnumerable. Obviamente, se você deseja velocidade especialmente para N grandes, pode sobrecarregar o acesso indexado. O último elimina a necessidade de iterar sobre grandes quantidades de dados desnecessários e será muito mais rápido do que a cláusula Where. Fornecer ambas as sobrecargas permite que o compilador selecione a variante mais adequada.

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
fonte
Isso funciona para qualquer lista? porque eu tento usar em uma lista para uma classe personalizada e retornar um IEnumarted <class> em vez de <class> e forçar List.GetNth (1) de coversion (classe) não funciona nem.
Juan Carlos Oropeza
Foi minha culpa ter que incluir 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();
}

resultado

insira a descrição da imagem aqui

Anwar Ul Haq
fonte
0

Imho nenhuma resposta está certa. Todas as soluções começam em 0. Mas eu quero ter o enésimo elemento real

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
fonte
0

@belucha Eu gosto disso, porque o código do cliente é muito legível e o compilador escolhe a implementação mais eficiente. Eu construiria sobre isso reduzindo os requisitos IReadOnlyList<T>e para salvar a Divisão para LINQ de alto desempenho:

    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
fonte