1. Базові способи фільтрації колекцій
Фільтрація — це як сито для золота: замість самородків ми відбираємо з колекції лише ті елементи, які нам потрібні. Наприклад, маємо список користувачів — хочеться знайти лише повнолітніх, або лише студентів, або лише тих, хто любить каву — програмістів, одним словом. Відбір відповідних елементів — дуже поширене завдання, яке трапляється всюди: від роботи з базами даних до обробки користувацького введення.
Розгляньмо різні способи фільтрації у C#. Поступово розвиватимемо наш навчальний консольний застосунок, додаючи до нього фільтри.
Ручна фільтрація за допомогою циклу
Почнімо з найпростішого й класичного способу: фільтрації за допомогою foreach:
// Приклад: є список чисел, треба вибрати тільки парні
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
List<int> evenNumbers = new List<int>();
foreach (int number in numbers)
{
if (number % 2 == 0) // якщо число парне
{
evenNumbers.Add(number);
}
}
Console.WriteLine("Парні числа:");
foreach (int n in evenNumbers)
{
Console.WriteLine(n);
}
Такий спосіб працює завжди, його легко зрозуміти, але він не найкомпактніший і не найсучасніший.
Чому ручна фільтрація не завжди зручна?
Коли у вас з’являється інша колекція чи інший критерій фільтрації, або потрібно писати одразу кілька фільтрів, виникає багато схожого коду. Важливо зберігати компактність, читабельність і модульність.
2. Фільтрація за складними критеріями
Припустімо, маємо колекцію об’єктів. Візьмемо приклад із користувачами нашого застосунку.
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public bool IsStudent { get; set; }
}
Оголошуємо список користувачів:
List<User> users = new List<User>
{
new User { Name = "Аня", Age = 17, IsStudent = true },
new User { Name = "Борис", Age = 21, IsStudent = false },
new User { Name = "Віка", Age = 19, IsStudent = true },
new User { Name = "Гліб", Age = 25, IsStudent = false }
};
Приклад 1: Відібрати всіх студентів
List<User> students = new List<User>();
foreach (User user in users)
{
if (user.IsStudent)
students.Add(user);
}
Console.WriteLine("Список студентів:");
foreach (User user in students)
{
Console.WriteLine($"{user.Name} ({user.Age})");
}
Приклад 2: Відібрати повнолітніх студентів
List<User> adults = new List<User>();
foreach (User user in users)
{
if (user.Age >= 18 && user.IsStudent)
adults.Add(user);
}
Console.WriteLine("Повнолітні студенти:");
foreach (User user in adults)
{
Console.WriteLine($"{user.Name} ({user.Age})");
}
А якщо критерії фільтрації динамічні?
Можна винести умови фільтрації в окремі методи й викликати їх усередині циклу:
bool IsAdult(User u) { return u.Age >= 18; }
bool IsStudent(User u) { return u.IsStudent; }
List<User> filtered = new List<User>();
foreach (User user in users)
{
if (IsAdult(user) && IsStudent(user))
filtered.Add(user);
}
3. Фільтрація за ключем у словниках
Списки й масиви — це зручно, але часто ми також працюємо зі словниками.
Припустімо, маємо словник, де ключ — імʼя користувача, а значення — його вік:
Dictionary<string, int> ageByName = new Dictionary<string, int>
{
["Аня"] = 17,
["Борис"] = 21,
["Віка"] = 19,
["Гліб"] = 25
};
Завдання: відібрати лише користувачів 18+
Dictionary<string, int> adults = new Dictionary<string, int>();
foreach (var pair in ageByName)
{
if (pair.Value >= 18)
adults.Add(pair.Key, pair.Value);
}
Console.WriteLine("Повнолітні:");
foreach (var pair in adults)
{
Console.WriteLine($"{pair.Key}: {pair.Value} років");
}
Завдання: відібрати користувачів з іменами на літеру «В»
Dictionary<string, int> namesWithV = new Dictionary<string, int>();
foreach (var pair in ageByName)
{
if (pair.Key.StartsWith("В"))
namesWithV.Add(pair.Key, pair.Value);
}
Console.WriteLine("Імена на букву 'В':");
foreach (var pair in namesWithV)
{
Console.WriteLine($"{pair.Key}: {pair.Value} років");
}
4. Принципи фільтрації
Як працює процес фільтрації
┌──────────────────────────────────┐
│ Список: [1, 2, 3, 4, 5, 6] │
└──────────────────────────────────┘
│
▼
[Перевірка: n % 2 == 0]
│
▼
┌──────────────────────────────────┐
│ Результат: [2, 4, 6] │
└──────────────────────────────────┘
Композиція фільтрів: фільтруємо за різними умовами
Можна виконувати послідовні фільтрації, якщо потрібно відбирати дані поетапно:
// Спочатку фільтруємо тільки студентів
List<User> onlyStudents = new List<User>();
foreach (User user in users)
{
if (user.IsStudent)
onlyStudents.Add(user);
}
// Потім вибираємо серед них тільки повнолітніх
List<User> adultStudents = new List<User>();
foreach (User user in onlyStudents)
{
if (user.Age >= 18)
adultStudents.Add(user);
}
Або все одразу:
List<User> adultStudents = new List<User>();
foreach (User user in users)
{
if (user.IsStudent && user.Age >= 18)
adultStudents.Add(user);
}
Сортування та інші операції
Сортування можна виконувати за допомогою методу Sort для списків:
// Повнолітні студенти, відсортовані за віком
adultStudents.Sort((a, b) => a.Age.CompareTo(b.Age));
Докладніше про сортування поговоримо на наступній лекції!
5. Фільтрація в користувацьких сценаріях
1. Фільтрація введення користувача
Припустімо, наш застосунок приймає список оцінок і має відібрати всі «п’ятірки».
Console.Write("Введи оцінки через пробіл: ");
string input = Console.ReadLine();
List<int> grades = new List<int>();
foreach (string s in input.Split(' '))
{
if (int.TryParse(s, out int grade))
grades.Add(grade);
}
List<int> fives = new List<int>();
foreach (int grade in grades)
{
if (grade == 5)
fives.Add(grade);
}
Console.WriteLine("П'ятірки:");
foreach (var grade in fives)
{
Console.WriteLine(grade);
}
2. Фільтрація за множиною умов
Завдання: відібрати користувачів-студентів віком від 18 до 22 років включно.
List<User> filtered = new List<User>();
foreach (User user in users)
{
if (user.IsStudent && user.Age >= 18 && user.Age <= 22)
filtered.Add(user);
}
3. Фільтрація за наявністю елемента
Нехай маємо список рядків, і ми хочемо відібрати лише ті, які містять підрядок ".net" (без урахування регістру).
List<string> technologies = new List<string> { "C#", ".NET", "Java", "dotnet", "JavaScript" };
List<string> netTechs = new List<string>();
foreach (var tech in technologies)
{
if (tech.ToLower().Contains(".net"))
netTechs.Add(tech);
}
foreach (var tech in netTechs)
{
Console.WriteLine(tech);
}
6. Особливості та типові помилки при фільтрації
Фільтрація здається простою, але помилитися легко. Розгляньмо кілька особливостей.
Зміна вихідної колекції
Якщо ви фільтруєте вихідну колекцію, а потім змінюєте її вміст після фільтрації, — результат не зміниться, якщо ви вже створили новий список. Якщо ж ви просто запам’ятали посилання на старий список, зміни у вихідній колекції можуть вплинути на підсумок.
List<int> filtered = new List<int>();
foreach (int n in numbers)
{
if (n > 2)
filtered.Add(n);
}
// Внесемо зміну у вихідний список
numbers.Add(10);
foreach (var n in filtered)
{
Console.WriteLine(n); // 10 сюди не потрапить
}
Результат ручної фільтрації
Результатом ручної фільтрації зазвичай є новий змінний список (наприклад, List<T>), з яким можна робити все потрібне — додавати, видаляти елементи тощо.
Фільтрація і продуктивність
Фільтри в циклах виконуються для кожного елемента колекції, тож якщо функція всередині умови «важка» — будьте обережні. Іноді доцільніше використати звичайний цикл foreach, особливо якщо логіка складна й потрібне логування чи додаткові дії.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ