Получение индекса элемента используя LINQ

1349269203_help_index

Иногда имея массив или список, нужно отобрать индексы тех элементов, которые удовлетворяют некое условие. Как мы знаем для выборки элементов идеально подходит LINQ. Но оказывается, что при отборе индексов элементов используя LINQ есть некоторые нюансы.


Для того чтобы применить LINQ к объекту, нужно чтобы он реализовывал интерфейс IEnumerable или IEnumerable. По своей сути интерфейс IEnumerable предсталяет неупорядоченную коллекцию, которая должна поддерживать семантику цикла foreach. Не смотря на то что большинство коллекций IEnumerable являются упорядоченными, некоторые (такие как Dictionary или HashSet) таковыми не являются.
Поэтому в LINQ нету метода по типу IndexOf.

Представим что у нас есть список чисел, и мы хотим выбрать индексы тех из них, которые являются четными. Сделать это с помощью LINQ можно так:

            var list = new List<int>() { 11, 4, 3, 7, 12};

            var indexes = list.Select((item, index) => new { Item = item, Index = index })
                     .Where(n => n.Item % 2 == 0)
                     .Select(n => n.Index);

            Console.WriteLine("Even numbers:");
            foreach (var index in indexes)
            {
                Console.WriteLine("{0} [{1}]", list[index], index);
            }

Идея в том, что сначала надо сформировать коллекцию анонимных объектов с полями Item и Index (объект из исходной коллекции и его индекс), а потом уже из этих объектов отобрать нужные.

Приведенный код выведет следующий результат:

Even numbers:
4 [1]
12 [4]

Такое решение возможно благодаря тому, что у метода Select() есть перегруженная версия:

public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, int, TResult> selector);

Тут параметр selector это делегат, вторым параметром которого является целочисельный индекс элемента.

Если хотите что бы ваш код выглядел более элегантно и читабельно, то можно создать метод расширения:

        public static IEnumerable<int> IndexesWhere<T>(this IEnumerable<T> source, Func<T, bool> predicate)
        {
            int index = 0;
            foreach (T element in source)
            {
                if (predicate(element))
                {
                    yield return index;
                }
                index++;
            }
        }

Тогда изначальное решение перепишется так:

            var list = new List<int>() { 11, 4, 3, 7, 12};
            .Select(n => n.Index);

            var indexes = list.IndexesWhere(n => n % 2 == 0);

            Console.WriteLine("Even numbers:");
            foreach (var index in indexes)
            {
                Console.WriteLine("{0} [{1}]", list[index], index);
            }
Источники примеров можно скачать тут: http://devnuances.com/examples/c_sharp/SelectElementsIndexesUsingLINQ.zip

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>