В LINQ есть такой необычный оператор, как SelectMany
. В чем же его необычность?
Рассмотрим сигнатуру метода:
1 | IEnumerable<R> SelectMany(this IEnumerable<T> items, Func<T, IEnumerable<R>> f) |
Из сигнатуры метода SelectMany
видно, что он применяется к последовательности типа IEnumerable<T>
. К каждому элементу этой последовательности применяется функция Func<T, IEnumerable<R>>
, которая преобразует элемент типа T
в последовательность типа IEnumerable<R>
. И на выходе мы имеем последовательность типа IEnumerable<R>
. Получается, что каждый элемент превращается в множество, которое может быть закрыто отличным типом от исходного, которое затем спрямляется. Результатом работы SelectMany
является конкатенация всех полученных последовательностей, т.е. мы имеем не последовательность последовательностей, а единую последовательность типа, отличного от исходного (не обязательно отличного, можно получить и последовательность того же типа).
Поясним вышесказанное следующим примером:
1 | string[] words = {"ab", "", "c", "de"}; |
Впрочем строка уже сама по себе является последовательностью символов и реализует интерфейс IEnumerable
Одно из не совсем очевидных применений SelectMany
— это вычисление декартова произведения двух множеств. Декартово произведение множества {-1, 0, 1} на само себя даст все возможные относительные координаты соседей условной точки Point, где Point – класс, имеющий координаты X и Y в качестве открытых полей. Для вычисления декартова произведения двух множеств также потребуется использовать метод Select
внутри SelectMany
, как показано в примере ниже:
1 | public static IEnumerable<Point> GetNeighbours(Point p) |
Вот ещё один интересный пример использования SelectMany
. Требуется составить лексикографически упорядоченный список всех встречающихся слов в массиве строк. Слова нужно сравнивать регистронезависимо и выводить в нижнем регистре.
1 | public static void Main() |
Здесь для разбиения строки на слова используется класс Regex
из пространства имён System.Text.RegularExpressions
. Все полученные слова “спрямляются” (объединяются из разных массивов в один массив), удаляются пустые строки и повторы. В конце массив лексикографически упорядочивается и возвращается.
Без использования SelectMany
пришлось бы очень трудно, т.к. каждая входящая строка после разбиения Regex.Split
превращается в массив слов и мы бы получили массив массивов.
Вывод программы можно увидеть ниже:
1 | a |