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!


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);

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?


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

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);

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..

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.


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

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


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

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];
        }
    }
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.