¿Cómo puedo obtener cada enésimo elemento de una List <T>?

115

Estoy usando .NET 3.5 y me gustaría poder obtener todos los * n* elementos de una lista. No me preocupa si se logra usando una expresión lambda o LINQ.

Editar

Parece que esta pregunta provocó bastante debate (lo cual es bueno, ¿no?). Lo principal que he aprendido es que cuando crees que sabes todas las formas de hacer algo (incluso tan simple como esto), ¡piénsalo de nuevo!

Paul Suart
fuente
No eliminé ningún significado detrás de su pregunta original; Solo lo limpié y usé las mayúsculas y la puntuación correctamente. (.NET está en mayúsculas, LINQ está en mayúsculas y no es una 'lambda', es una 'expresión lambda').
George Stocker
1
Reemplazó "alborotado" por "seguro" que no son sinónimos.
mqp
Parecería que sí. Tener seguro tampoco tiene sentido, a menos que sea "No estoy seguro si se puede lograr usando ..."
Samuel
Sí, según tengo entendido, eso es correcto.
mqp
fussed probablemente sería mejor reemplazarlo por "preocupado" para que diga "No me preocupa si se logra usando una expresión lambda o LINQ".
TheTXI

Respuestas:

190
return list.Where((x, i) => i % nStep == 0);
mqp
fuente
5
@mquander: Tenga en cuenta que esto le dará el elemento n-1. Si desea los n-ésimos elementos reales (omitiendo el primero), tendrá que agregar 1 a i.
casperOne
2
Sí, supongo que depende de lo que quieras decir con "enésimo", pero tu interpretación podría ser más común. Sume o reste de i según sus necesidades.
mqp
5
Solo para tener en cuenta: la solución Linq / Lambda tendrá un rendimiento mucho menor que un bucle simple con incremento fijo.
MartinStettner
5
No necesariamente, con ejecución diferida podría usarse en un bucle foreach y solo recorre la lista original una vez.
Samuel
1
Depende de lo que entiendas por "práctico". Si necesita una forma rápida de obtener todos los demás elementos de una lista de 30 elementos cuando el usuario hace clic en un botón, diría que es igual de práctico. A veces, el rendimiento ya no importa. Por supuesto, a veces lo hace.
mqp
37

Sé que es "de la vieja escuela", pero ¿por qué no usar un bucle for con stepping = n?

Michael Todd
fuente
Ese fue básicamente mi pensamiento.
Mark Pim
1
@Michael Todd: Funciona, pero el problema con eso es que tienes que duplicar esa funcionalidad en todas partes. Al utilizar LINQ, se convierte en parte de la consulta compuesta.
casperOne
8
@casperOne: Creo que los programadores inventaron esta cosa llamada subrutinas para lidiar con esto;) En un programa real probablemente usaría un bucle, a pesar de la versión inteligente de LINQ, ya que un bucle significa que no tienes que iterar sobre cada elemento ( incrementar el índice en N.)
mqp
Estoy de acuerdo en optar por la solución de la vieja escuela, e incluso supongo que esto funcionará mejor.
Jesper Fyhr Knudsen
Fácil de dejarse llevar por la nueva y elegante sintaxis. Aunque es divertido.
Ronnie
34

Suena como

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

haría el truco. No veo la necesidad de usar Linq o expresiones lambda.

EDITAR:

Hazlo

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

y escribes a la manera de LINQish

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

2da edición :

Para hacerlo aún más LINQish

from var i in Range(0, ((myList.Length-1)/n)+1) select list[n*i];
MartinStettner
fuente
2
Me gusta este método para usar el getter this [] en lugar del método Where (), que esencialmente itera cada elemento de IEnumerable. Si tiene un tipo IList / ICollection, este es el mejor enfoque, en mi humilde opinión.
spoulson
No estoy seguro de cómo funciona la lista, pero ¿por qué usa un bucle y regresa en list[i]lugar de regresar list[n-1]?
Juan Carlos Oropeza
@JuanCarlosOropeza devuelve cada enésimo elemento (por ejemplo, 0, 3, 6 ...), no solo el enésimo elemento de la lista.
alfoks
27

Puede usar la sobrecarga Where que pasa el índice junto con el elemento

var everyFourth = list.Where((x,i) => i % 4 == 0);
JaredPar
fuente
1
Tengo que decir que soy fan de este método.
Quintin Robinson
1
Sigo olvidando que puedes hacer eso, muy bien.
Stephen Newman
10

En bucle

for(int i = 0; i < list.Count; i += n)
    //Nth Item..
Quintin Robinson
fuente
Count evaluará el enumerable. si esto se hizo de una manera amigable con linq, entonces podría evaluar perezosamente y tomar el primer valor 100, por ejemplo, `` source.TakeEvery (5) .Take (100) '' Si la fuente subyacente fuera costosa de evaluar, entonces su enfoque causaría que cada elemento a evaluar
RhysC
1
@RhysC Buen punto, para enumerables en general. OTOH, Pregunta especificó List<T>, por lo que Countse define como barato.
ToolmakerSteve
3

No estoy seguro de si es posible hacerlo con una expresión LINQ, pero sé que puede usar el Wheremétodo de extensión para hacerlo. Por ejemplo, para obtener cada quinto artículo:

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

Esto obtendrá el primer elemento y cada quinto a partir de ahí. Si desea comenzar en el quinto elemento en lugar del primero, compare con 4 en lugar de comparar con 0.

Guffa
fuente
3

Creo que si proporciona una extensión linq, debería poder operar en la interfaz menos específica, por lo tanto, en IEnumerable. Por supuesto, si está preparado para la velocidad, especialmente para N grandes, puede proporcionar una sobrecarga para el acceso indexado. Este último elimina la necesidad de iterar sobre grandes cantidades de datos no necesarios y será mucho más rápido que la cláusula Where. Proporcionar ambas sobrecargas permite al compilador seleccionar la variante más adecuada.

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
fuente
¿Esto funciona para cualquier lista? porque trato de usar en una Lista para una clase personalizada y devuelvo un IEnumarted <clase> en lugar de <clase> y forzar la cobertura (clase) List.GetNth (1) tampoco funciona.
Juan Carlos Oropeza
Fue mi culpa tener 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();
}

salida

ingrese la descripción de la imagen aquí

Anwar Ul Haq
fuente
0

En mi humilde opinión, ninguna respuesta es correcta. Todas las soluciones comienzan desde 0. Pero quiero tener el n-é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];
}
usuario2340145
fuente
0

@belucha Me gusta esto, porque el código del cliente es muy legible y el compilador elige la implementación más eficiente. Me basaría en esto reduciendo los requisitos IReadOnlyList<T>y guardando la División para LINQ de alto rendimiento:

    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
fuente