January 27, 2023

LINQ

Обзор методов и примеры применения

Фильтрация

.Where - определяет фильтр выборки

string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };
var query = names.Where(name => name.EndsWith("y"));
foreach(string d in query){project.SendInfoToLog(d, true);}

.Take - выбирает определенное количество элементов

string[] numbers = { "Один", "Два", "Три", "Четыре", "Пять" };
var query = numbers.Where(b => b.Contains("р")).OrderBy(b => b).Take(2);
foreach(string d in query){project.SendInfoToLog(d, true);}

.Skip - пропускает определенное количество элементов

string[] numbers = { "Один", "Два", "Три", "Четыре", "Пять" };
var query = numbers.Where(b => b.Contains("р")).OrderBy(b => b).Skip(1);
foreach(string d in query){project.SendInfoToLog(d, true);}

.TakeWhile - возвращает цепочку элементов последовательности, до тех пор, пока условие истинно

int[] numbers = { 3, 5, 2, 234, 4, 1 };
var takeWhileSmall = numbers.TakeWhile(n => n < 100); //{3,5,2}
foreach(int d in takeWhileSmall){project.SendInfoToLog(d.ToString(), true);}

.SkipWhile - пропускает элементы в последовательности, пока они удовлетворяют заданному условию, и затем возвращает оставшиеся элементы

int[] numbers = { 3, 5, 2, 234, 4, 1 };
var skipWhileSmall = numbers.SkipWhile(n => n < 100) ; // { 234, 4 , 1 }
foreach(int d in skipWhileSmall){project.SendInfoToLog(d.ToString(), true);}

.Distinct - удаляет из последовательности повторяющиеся элементы

Проецирование

.Select - определяет что добавить в выборку

//Объединение элемента с его индексом
string[] names = { «Tom», «Dick», «Harry», «Mary», «Jay» }; 

var query = names.Select ((s, i) => i + "=" + s); //{ "0=Tom", "1=Dick", …)

.SelectMany - проецирует элементы (другой тип) и делает плоской вложенную иерархию элементов

//Разбить элементы
string[] fullNames = { "Anne Williams", "John Fred Smith", "Sue Green" };

var query = fullNames.SelectMany(name => name.Split()); 
foreach(string name in query)
   project.SendInfoToLog(name + "|", true); //AnneIWilliamsIJohn|Fred|Smith|Sue|Green|
//Перекрёстное соединение списков
int [] numbers ={1,2,3}; 
string[] letters = { "a", "b" }; 

var query = from n in numbers 
from l in letters 
select n.ToString() + l; 
foreach(string str in query){project.SendInfoToLog(str, true);}//{ "1a", "1b", "2a", "2b", "3a", "3b" }
//Сопоставление элементов друг с другом
string[] players = { "Tom", "Jay", "Mary" };

var query = from name1 in players
from name2 in players
where name1.CompareTo(name2) < 0
orderby name1, name2
select name1 + " vs " + name2; 
foreach(string str in query){project.SendInfoToLog(str, true);} //{ "Jay vs Mary", "Jay vs Tom", "Mary vs Tom" }

Соединение

.Join (по совпадающим индексам)

.GroupJoin - выполняет одновременно соединение коллекций и группировку элементов по ключу

.Zip - объединяет две коллекции в соответствии с определенным условием

//совмещение последовательностей
int[] numbers = { 3, 5, 7 }; 
string[] words = { "three", "five", "seven", "ignored" }; 
var zip = numbers.Zip(words, (n, w) => n + "=" + w); //3=three, 5=five...

Упорядочение

.OrderBy - упорядочивает элементы по возрастанию

.ThenBy - делает сортировку по дополнительному критерию (или нескольким) после выполнения операции OrderBy.

//сортировка в алфавитном порядке
var query = names.OrderBy(s => s) ;
//сортировка по длине
var query = names.OrderBy(s => s.Length);
//одновременно по размеру и в алфавитном порядке
var query = names.OrderBy(s => s.Length).ThenBy (s => s);
//сортировка сначала по длине, затем по второму символу и по первому символу
var names.OrderBy(s => s.Length).ThenBy(s => s[l]).ThenBy(s => s[0]);
//сортировка без чувствительности к регистру
var stroka = list.OrderBy(n => n, StringComparer.CurrentCultureIgnoreCase);

.OrderByDescending и .ThenByDescending сортируют в убывающем порядке.

.Reverse возвращает последовательность в обратном порядке.

Группирование

.ToLookup - группирует элементы по ключу, при этом все элементы добавляются в словарь.

.GroupBy - Группирует последовательность в подпоследовательности. Позволяет группировать элементы по заданным критериям и выполнять над группами дополнительные операции, а так же группировать по одному или нескольким параметрам.

//получить элементы файлов по расширениям в группах
string[] files = Directory.GetFiles (@"D:\Trading");
var query = files.GroupBy(file => Path.GetExtension (file));
foreach(var str in query){project.SendInfoToLog("Extension: " + str.Key, true);
	foreach(var s in str){
		project.SendInfoToLog(" --" + s, true);
	}
}

Операции над множествами

.Concat - возвращает все элементы из первой последовательности, за которыми следуют все элементы из второй последовательности.

.Union - делает то же самое, но удаляет любые дубликаты.

int [] seql = {1,2,3}, seq2 = { 3, 4, 5 };
//var concat = seql.Concat(seq2); // { 1, 2, 3, 3, 4, 5 }
var union = seql.Union(seq2); // { 1, 2, 3, 4, 5 }
foreach(int s in union){project.SendInfoToLog(s.ToString(), true);}

.Intersect - возвращает элементы, имеющиеся в двух последовательностях.

.Except - возвращает элементы из первой последовательности, которые не присутствуют во второй последовательности.

int [] seql = {1,2,3}, seq2 = { 3, 4, 5 };
var concat = seql.Except(seq2); // { 1, 2 }
//var union = seql.Intersect(seq2); // { 3 }
foreach(int s in concat){project.SendInfoToLog(s.ToString(), true);}

Методы преобразования

.ToArray - в массив

.ToList - в список

Операции над элементами

.First - берёт первое

.Last - берёт последнее

LastOrDefault - выбирает последний элемент коллекции или возвращает значение по умолчанию

.Single - требует точно одного совпадающего элемента

FirstOrDefault - выбирает первый элемент коллекции или возвращает значение по умолчанию

SingleOrDefault - выбирает первый элемент коллекции или возвращает значение по умолчанию

int[] numbers = {1, 2, 3, 4, 5}; 
int first = numbers.First(); // 1 
int last = numbers.Last() ; // 5
int firstEven = numbers.First(n => n % 2 == 0); // 2 (первое чётное)
int lastEven = numbers.Last(n => n % 2 == 0); // 4 (последнее чётное)
int firstBigError = numbers.First(n => n > 10); // Генерируется исключение 
int firstBigNumber = numbers.FirstOrDefault(n => n > 10); // 0
int onlyDivBy3 = numbers.Single(n => n % 3 == 0) ; //3 
int divBy2Err = numbers.Single(n => n % 2 == 0); // Ошибка: совпадение дают 2 и 4 
int singleError = numbers.Single(n => n > 10); // Ошибка 
int noMatches = numbers.SingleOrDefault(n => n > 10); // 0 
int divBy2Error = numbers.SingleOrDefault(n => n % 2 == 0); // Ошибка

.ElementAt - берёт указанный элемент по индексу

.ElementAtOrDefault - выбирает элемент коллекции по определенному индексу или возвращает значение по умолчанию, если индекс вне допустимого диапазона

int[] numbers ={1, 2, 3, 4, 5}; 
int third = numbers.ElementAt(2); // 3 
int tenthError = numbers.ElementAt(9); // Генерируется исключение 
int tenth = numbers.ElementAtOrDefault(9); // 0

Методы агрегирования

.Count - возвращает кол-во элементов

.Min и .Max - возвращают наименьший или наибольший элемент из последовательности.

int[] numbers = { 28, 32, 14 }; 
int smallest = numbers.Min(); // 14; 
int largest = numbers.Max(); // 32;

Если указано выражение селектора, то каждый элемент сначала проецируется: 
int smallest = numbers.Max(n => n % 10); // 8;

Sum и Average представляют собой операции агрегирования, которые применяются подобно Min и Мах:

decimal [] numbers = { 3, 4, 8 }; 
decimal sumTotal = numbers.Sum(); // 15 
decimal average = numbers.Average(); //5 (среднее значение)
//подсчитать общую длину всех строк в массиве
int combinedLength = names.Sum(s => s.Length); // 19

.Aggregate - позволяет указывать специальный алгоритм накопления для реализации необычных агрегаций.

//0 - начальное значение, с которого стартует накопление
int [ ] numbers ={1,2,3}; 
int x = numbers.Aggregate(0, (prod, n) => prod * n) ; // 0*1*2*3 = 0 
int у = numbers.Aggregate( (prod, n) => prod * n) ; // 1*2*3 = 6

Квантификаторы

.Contains - возвращает true, если заданный элемент присутствует в последовательности.

bool hasAThree = new int[] { 2, 3, 4 }.Contains(3); // true

.Any - возвращает true, если указанное выражение дает значение true хотя бы для одного элемента.

bool hasAThree = new int[] { 2, 3, 4 }.Any(n => n == 3); // true

Операция Any может делать все, что делает Contains, и даже больше:

bool hasABigNumber = new int[] { 2, 3, 4 }.Any(n => n > 10); // false

Вызов метода Any без предиката приводит к возвращению true, если последовательность содержит один или большее число элементов.

bool hasABigNumber = new int[] { 2, 3, 4 }.Where(n => n > 10).Any();

.All - определяет, все ли элементы коллекции удовлетворяют определенному условию и возвращает true, если все элементы удовлетворяют предикату.

dataContext.Customers.Where(с => с.Purchases.All(р => р.Price < 100));

.SequenceEqual - сравнивает две последовательности. Для возвращения true обе последовательности должны иметь идентичные элементы, расположенные в одинаковом порядке.

Методы генерации

.Empty используется для создания пустой последовательности значений. Им можно создать пустой массив или список.

.Range - создаёт элементы в диапазоне

foreach (int i in Enumerable.Range (5, 3)) 
   Console.Write(i + " "); //567 

.Repeat принимает элемент, подлежащий повторению, и количество повторений.

foreach (bool х in Enumerable.Repeat(true, 3)) 
   Console.Write (x + " "); // True True True

Несколько своих примеров решения задач:

//Вывести все числа от 10 до 50, разделённые запятыми
string numbers = string.Join(",", Enumerable.Range(10, 41));
project.SendInfoToLog(numbers, true);
//Вывести только те числа от 10 до 50, которые можно разделить на 3
string numbers = string.Join(", ", Enumerable.Range(10, 41).Where(n => n % 3 == 0));
project.SendInfoToLog(numbers, true);
//Вывести слово "Linq" 10 раз
string numbers = string.Join(" ", Enumerable.Repeat("Linq", 10));
project.SendInfoToLog(numbers, true);
//Вывести все слова с буквой "a"
var query = "aaa;abb;ccc;dap";
var str = query.Split(';').Where(x => x.Contains("a"));
foreach(string d in str){project.SendInfoToLog(d, true);}
//Вывести количество букв 'a' в словах с этой буквой через запятую
var query = "aaa;abb;ccc;dap";
string numbers = string.Join(",", query.Split(';').Select(x => x.Count(c => c == 'a')));
project.SendInfoToLog(numbers, true);
//Вывести true, если слово "abb" существует, иначе false
var query = "aaa;xabbx;abb;ccc;dap";
var str = (query.Split(';').Select(x => x).Any(c => c == "abb")).ToString();
project.SendInfoToLog(str, true);
//Вывести самое длинное слово в строке
var query = "aaa;xabbx;abb;ccc;dap";
var str = query.Split(';').OrderByDescending(z => z.Length).First();
project.SendInfoToLog(str, true);
//Вычислить среднюю длину слов в строке
var query = "aaa;xabbx;abb;ccc;dap";
var str = query.Split(';').Average(x => x.Length);
project.SendInfoToLog(str.ToString(), true);
//Вывести самое короткое слово в реверсе
var query = "aaa;xabbx;abb;ccc;dap;zh";
var str = query.Split(';').OrderBy(n => n.Length).Select(x => new string(x.Reverse().ToArray())).First();
project.SendInfoToLog(str, true);
//Вывести true, если в первом слове, начинающемся с "aa", все буквы - "b", иначе false 
var query = "baaa;aabb;aaa;xabbx;abb;ccc;dap;zh";
var str = query.Split(';').Where(n => n.StartsWith("aa")).Where(n => n.Substring(2).All(ch => ch == 'b')).Any();
project.SendInfoToLog(str.ToString(), true);