Как я могу получить каждый 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);

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

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

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.


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

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];
        }
    }
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.