Як я можу отримати кожен n-й елемент зі списку <T>?


114

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

Редагувати

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


Я не редагував жодного сенсу, що стоїть за вашим початковим запитанням; Я лише очистив його та правильно використав великі літери та пунктуацію. (. NET з великої літери, LINQ є у All-Caps, і це не "лямбда", це "лямбда-вираз".)
Джордж Стокер,

1
Ви замінили "fussed" на "sure", які зовсім не є синонімами.
mqp

Здавалося б, так. Переконайтесь, що сенс теж не має сенсу, якщо це не "я не впевнений, чи можна досягти використання ..."
Самуїл,

Так, як я розумію, це правильно.
mqp

метушні, мабуть, найкраще буде замінити на "заклопотаний", щоб він писав "Я не переймаюся тим, чи досягнуто це за допомогою лямбда-виразу чи LINQ".
TheTXI

Відповіді:


189
return list.Where((x, i) => i % nStep == 0);

5
@mquander: Зауважте, що це фактично дасть вам n-й елемент. Якщо ви хочете фактично n-й елементів (пропускаючи перші), вам доведеться додати 1 до i.
casperOne

2
Так, я вважаю, що це залежить від того, що ви маєте на увазі під "nth", але ваше тлумачення може бути більш поширеним. Додайте або віднімайте від i відповідно до ваших потреб.
mqp

5
Зауважимо лише: рішення Linq / Lambda буде набагато менш ефективною, ніж звичайна петля з фіксованим збільшенням.
MartinStettner

5
Не обов'язково, при відкладеному виконанні його можна використовувати в циклі foreach і лише циклічно над початковим списком один раз.
Самуїл

1
Це залежить від того, що ви маєте на увазі під "практичним". Якщо вам потрібен швидкий спосіб потрапити будь-який інший товар у список із 30 елементів, коли користувач натискає кнопку, я б сказав, що це кожен біт як практичний. Іноді продуктивність справді вже не має значення. Звичайно, іноді так і відбувається.
mqp

37

Я знаю, що це "стара школа", але чому б просто не використати цикл із кроком = n?


В основному це була моя думка.
Марк Пім

1
@Michael Todd: Це працює, але проблема в тому, що вам потрібно дублювати цю функціональність скрізь. Використовуючи LINQ, він стає частиною складеного запиту.
casperOne

8
@casperOne: Я вважаю, що програмісти винайшли цю річ під назвою підпрограми для вирішення цього питання;) У реальній програмі я б, мабуть, використовував цикл, незважаючи на розумну версію LINQ, оскільки цикл означає, що вам не потрібно перебирати кожен елемент ( приріст індексу на N.)
mqp

Я згоден піти на рішення старої школи, і я навіть здогадуюсь, що це буде краще.
Jesper Fyhr Knudsen

Легко захоплюватися фантазійним новим синтаксисом. Це весело, хоча.
Ронні

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
Мені подобається цей метод використання цього [] геттера замість методу Where (), який по суті ітератує кожен елемент IEnumerable. Якщо у вас тип IList / ICollection, це кращий підхід, ІМХО.
подружжя

Не знаєте, як працює список, але чому ви використовуєте цикл і повертаєтеся, list[i]а не просто повертаєтесь list[n-1]?
Хуан Карлос Оропеза

@JuanCarlosOropeza він повертає кожен n-й елемент (наприклад, 0, 3, 6 ...), а не лише n-й елемент списку.
alfoks

27

Ви можете використовувати перевантаження Where, яке передає індекс разом з елементом

var everyFourth = list.Where((x,i) => i % 4 == 0);

1
Треба сказати, що я фанат цього методу.
Квінтін Робінсон,

1
Я постійно забуваю, що ти можеш це зробити - дуже приємно.
Стівен Ньюмен

10

Для циклу

for(int i = 0; i < list.Count; i += n)
    //Nth Item..

Граф оцінить численні. якби це було зроблено по-доброму по відношенню до linq, тоді ви могли ліниво оцінити і взяти перше значення 100, наприклад, `` source.TakeEvery (5). Take (100) `` Якщо базове джерело дорого оцінювалося, то ваш підхід спричинив би кожен елемент, який потрібно оцінювати
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, ви можете забезпечити перевантаження для індексованого доступу. Останнє усуває необхідність повторної повторної роботи над великою кількістю непотрібних даних, і буде набагато швидшою, ніж пропозиція «Де». Надання обох перевантажень дозволяє компілятору вибрати найбільш підходящий варіант.

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

Це працює для будь-якого списку? тому що я намагаюся використовувати у списку для користувальницького класу і повертати 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>та зберегти Відділ для високопродуктивного 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.