JavaRush /Курси /C# SELF /Фільтрація елементів

Фільтрація елементів

C# SELF
Рівень 29 , Лекція 0
Відкрита

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, особливо якщо логіка складна й потрібне логування чи додаткові дії.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ