Как я могу получить каждый n-й элемент из List <T>?

115

Я использую .NET 3.5 и хотел бы получить каждый * n* -й элемент из списка. Меня не беспокоит, достигается ли это с помощью лямбда-выражения или LINQ.

редактировать

Похоже, этот вопрос вызвал довольно много споров (что, правда, хорошо?). Главное, что я усвоил, это то, что когда вы думаете, что знаете все способы сделать что-то (даже такое простое), подумайте еще раз!

Пол Суарт
источник
Я не исправлял никакого смысла вашего исходного вопроса; Я только подчистил и правильно использовал заглавные буквы и пунктуацию. (.NET пишется заглавными буквами, LINQ - заглавными, и это не «лямбда», это «лямбда-выражение».)
Джордж Стокер,
1
Вы заменили «суетился» на «уверен», что вовсе не синонимы.
mqp
Казалось бы так. Уверенность тоже не имеет смысла, если только это не «Я не уверен, достижимо ли это с помощью ...»
Самуэль
Да, я так понимаю, это правильно.
mqp
fussed, вероятно, лучше всего было бы заменить на «обеспокоенный», чтобы он читался так: «Меня не волнует, достигается ли это с помощью лямбда-выражения или LINQ».
TheTXI,

Ответы:

191
return list.Where((x, i) => i % nStep == 0);
mqp
источник
5
@mquander: обратите внимание, что это фактически даст вам n-й - 1 элемент. Если вам нужны фактические n-е элементы (пропуская первый), вам нужно будет добавить 1 к i.
casperOne,
2
Да, я полагаю, это отчасти зависит от того, что вы подразумеваете под «nth», но ваша интерпретация может быть более общей. Добавьте или вычтите из i по своему усмотрению.
mqp
5
Обратите внимание: решение Linq / Lambda будет намного менее производительным, чем простой цикл с фиксированным приращением.
MartinStettner,
5
Не обязательно, с отложенным выполнением его можно было бы использовать в цикле foreach и только один раз перебирать исходный список.
Самуэль,
1
Это зависит от того, что вы подразумеваете под «практичным». Если вам нужен быстрый способ получить любой другой элемент в списке из 30 элементов, когда пользователь нажимает кнопку, я бы сказал, что это максимально практично. Иногда производительность действительно больше не имеет значения. Конечно, иногда бывает.
mqp
37

Я знаю, что это «старая школа», но почему бы просто не использовать цикл for со степпингом = n?

Майкл Тодд
источник
Это в основном была моя мысль.
Марк Пим,
1
@ Майкл Тодд: Это работает, но проблема в том, что вам нужно везде дублировать эту функциональность. При использовании LINQ он становится частью составного запроса.
casperOne,
8
@casperOne: Я считаю, что программисты изобрели эту штуку, называемую подпрограммами, чтобы справиться с этим;) В реальной программе я, вероятно, использовал бы цикл, несмотря на умную версию LINQ, поскольку цикл означает, что вам не нужно перебирать каждый элемент (
увеличить
Я согласен использовать старое школьное решение, и я даже предполагаю, что оно будет работать лучше.
Jesper Fyhr Knudsen
Легко увлечься новым причудливым синтаксисом. Хотя это весело.
Ronnie
34

Звучит как

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

сделает свое дело. Я не вижу необходимости использовать Linq или лямбда-выражения.

РЕДАКТИРОВАТЬ:

Сделай это

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

и вы пишете в стиле LINQish

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

2-е редактирование :

Чтобы сделать его еще более LINQish

from var i in Range(0, ((myList.Length-1)/n)+1) select list[n*i];
MartinStettner
источник
2
Мне нравится этот метод использования метода получения this [] вместо метода Where (), который по существу выполняет итерацию каждого элемента IEnumerable. Если у вас есть тип IList / ICollection, это лучший подход, IMHO.
spoulson
Не уверен, как работает список, но почему вы используете цикл и list[i]вместо этого просто return list[n-1]?
Хуан Карлос Оропеза,
@JuanCarlosOropeza возвращает каждый n-й элемент (например, 0, 3, 6 ...), а не только n-й элемент списка.
alfoks
27

Вы можете использовать перегрузку Where, которая передает индекс вместе с элементом

var everyFourth = list.Where((x,i) => i % 4 == 0);
JaredPar
источник
1
Должен сказать, что я фанат этого метода.
Quintin Robinson,
1
Я все время забываю, что ты можешь это сделать - очень мило.
Стивен Ньюман,
10

Для петли

for(int i = 0; i < list.Count; i += n)
    //Nth Item..
Квинтин Робинсон
источник
Count будет оценивать перечислимое. если бы это было сделано дружественным к linq способом, то вы могли бы лениво eval и взять первые 100 значений, например, `` source.TakeEvery (5) .Take (100) '' Если бы базовый источник был дорогостоящим для eval, то ваш подход вызвал бы все элемент для оценки
RhysC
1
@RhysC Хороший момент для перечисления в целом. OTOH, Вопрос не уточнял List<T>, значит Countопределяется как дешевый.
ToolmakerSteve
3

Я не уверен, можно ли это сделать с помощью выражения LINQ, но я знаю, что для этого можно использовать Whereметод расширения. Например, чтобы получить каждый пятый предмет:

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

Это получит первый предмет и каждый пятый оттуда. Если вы хотите начать с пятого элемента вместо первого, вы сравниваете с 4 вместо сравнения с 0.

Guffa
источник
3

Я думаю, что если вы предоставите расширение linq, вы сможете работать с наименее конкретным интерфейсом, то есть с IEnumerable. Конечно, если вы настроены на скорость, особенно для больших N, вы можете предоставить перегрузку для индексированного доступа. Последнее устраняет необходимость повторения больших объемов ненужных данных и будет намного быстрее, чем предложение Where. Наличие обеих перегрузок позволяет компилятору выбрать наиболее подходящий вариант.

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];
    }
}
Белуча
источник
Это работает для любого списка? потому что я пытаюсь использовать в List для настраиваемого класса и возвращать IEnumarted <class> вместо <class> и форсирование coversion (class) List.GetNth (1) тоже не работает.
Хуан Карлос Оропеса
По моей вине мне пришлось включить GetNth (1) .FirstOrDefault ();
Хуан Карлос Оропеса
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();
}

вывод

введите описание изображения здесь

Анвар Уль-Хак
источник
0

Имхо нет правильного ответа. Все решения начинаются с 0. Но я хочу иметь реальный n-й элемент.

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
источник
0

@belucha Мне это нравится, потому что код клиента очень удобочитаем, и компилятор выбирает наиболее эффективную реализацию. Я бы использовал это, уменьшив требования IReadOnlyList<T>и сохранив Division для высокопроизводительного 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
источник