1. Базові можливості пошуку в колекціях
Часто слова «пошук» і «фільтрація» сприймають як синоніми, але в програмуванні це різні речі.
- Фільтрація — це коли ми хочемо отримати всі елементи колекції, що відповідають певній умові (наприклад, усі числа, більші за 10).
- Пошук — коли зазвичай потрібен один елемент: перший відповідний, елемент із певним значенням або просто відповідь, чи є він узагалі в колекції.
Якщо провести аналогію з бібліотекою: фільтрація — це зібрати всі книжки на тему «Космос», а пошук — запитати: «Є у вас книжка „Улісс“?» або «Де стоїть перша книжка із синьою обкладинкою?».
Усі основні колекції .NET (масиви, списки, множини, словники тощо) підтримують кілька способів пошуку елементів або перевірки наявності.
Розгляньмо найпоширеніші методи:
| Колекція | Перевірити наявність | Знайти індекс | Знайти елемент | Знайти за ключем |
|---|---|---|---|---|
|
|
|
|
- |
(масив) |
/ |
|
- | - |
|
|
- | - | - |
|
, |
- | - | Має індексатор |
Деякі методи доступні лише для конкретних типів.
2. Пошук у списках: List<T>
List<T> — одна з найуніверсальніших колекцій для зберігання елементів із довільним доступом. Ось основні методи пошуку:
Перевірка наявності елемента: Contains
Цей метод повідомляє, чи міститься потрібний елемент у списку:
List<string> fruits = new List<string> { "Яблуко", "Банан", "Ківі" };
bool hasKiwi = fruits.Contains("Ківі"); // true
bool hasMango = fruits.Contains("Манго"); // false
Console.WriteLine(hasKiwi); // True
Під капотом Contains просто перебирає колекцію, використовуючи метод Equals.
Пошук індексу: IndexOf
Якщо треба дізнатися, де саме розташований елемент (його номер у списку):
int index = fruits.IndexOf("Банан"); // 1 (індексація з нуля)
int absentIndex = fruits.IndexOf("Кавун"); // -1 (якщо немає елемента)
Console.WriteLine(index);
Console.WriteLine(absentIndex);
Знайти перший відповідний: Find
Можна шукати не за конкретним значенням, а за умовою. Для цього використовують метод Find (або його індексний варіант — FindIndex):
// Знайти перший фрукт, назва якого довша за 4 символи
string longFruit = fruits.Find(fruit => fruit.Length > 4); // "Яблуко"
Console.WriteLine(longFruit);
Зверніть увагу: якщо нічого не знайдено, повертається значення за замовчуванням для типу (null для посилальних типів).
Пошук кількох елементів: FindAll
Якщо потрібна саме фільтрація (усі, що підходять), використовуйте FindAll:
// Усі фрукти, у назві яких є літера «і»
List<string> withI = fruits.FindAll(f => f.Contains('і'));
foreach (var fruit in withI)
Console.WriteLine(fruit); // "Ківі"
3. Пошук у масивах: методи класу Array
Масиви, на відміну від List<T>, не мають методу Find, зате для них існує статичний клас Array:
int[] numbers = { 1, 2, 3, 2, 4 };
int pos = Array.IndexOf(numbers, 2); // 1 — перший індекс двійки
int lastPos = Array.LastIndexOf(numbers, 2); // 3 — останній індекс
Console.WriteLine(pos + ", " + lastPos);
Якщо треба знайти елемент за умовою, можна використати простий цикл:
int firstGreaterThanTwo = -1;
for (int i = 0; i < numbers.Length; i++)
{
if (numbers[i] > 2)
{
firstGreaterThanTwo = numbers[i];
break;
}
}
Console.WriteLine(firstGreaterThanTwo); // 3
4. Універсальні методи для всіх колекцій
Багато колекцій надають методи для пошуку (наприклад, Contains, IndexOf, Find тощо). Якщо потрібно щось складніше, пишуть цикл перебору елементів.
Приклад: перевірити, чи є фрукт, що починається на «Б»
bool hasB = false;
foreach (var f in fruits)
{
if (f.StartsWith("Б"))
{
hasB = true;
break;
}
}
Console.WriteLine(hasB); // True
Приклад: знайти перший фрукт, що містить «і»
string withI = null;
foreach (var f in fruits)
{
if (f.Contains('і'))
{
withI = f;
break;
}
}
Console.WriteLine(withI); // "Ківі"
Приклад: пошук у користувацьких об’єктах
class Student
{
public string Name;
public int Group;
public int Id;
}
List<Student> students = new List<Student>
{
new Student { Name = "Іван", Group = 101, Id = 1 },
new Student { Name = "Марія", Group = 101, Id = 2 },
new Student { Name = "Петро", Group = 102, Id = 3 },
};
// Шукаємо студента з Id == 2
Student maria = null;
foreach (var s in students)
{
if (s.Id == 2)
{
maria = s;
break;
}
}
if (maria != null)
Console.WriteLine(maria.Name); // "Марія"
else
Console.WriteLine("Студента не знайдено");
5. Пошук у HashSet<T>: лише «є чи ні?»
Множини (HashSet<T>) створені для швидкої відповіді «є таке чи ні». Вони не забезпечують пошук за індексом, зате дуже швидко перевіряють наявність:
HashSet<int> set = new HashSet<int> { 1, 3, 5, 7 };
bool hasThree = set.Contains(3); // True
Console.WriteLine(hasThree);
// Якщо потрібен пошук за умовою (наприклад, чи є парні числа):
bool hasEven = false;
foreach (var x in set)
{
if (x % 2 == 0)
{
hasEven = true;
break;
}
}
Console.WriteLine(hasEven); // False
6. Пошук у словниках: Dictionary<TKey, TValue>
Словник — колекція пар «ключ — значення». Пошук за ключем — це його суперсила.
Перевірка наявності ключа
Dictionary<int, string> idToName = new Dictionary<int, string>
{
{ 1, "Вася" }, { 2, "Катя" }
};
if (idToName.ContainsKey(2))
Console.WriteLine(idToName[2]); // "Катя"
Пошук значення за ключем: безпечно!
if (idToName.TryGetValue(3, out string result))
Console.WriteLine(result);
else
Console.WriteLine("Немає студента з таким Id"); // Це і виведеться
Пошук за значенням (рідко і повільно):
bool containsVasya = idToName.ContainsValue("Вася");
Console.WriteLine(containsVasya); // True
Знайти запис за умовою за значенням або ключем
// Перший ID, де імʼя починається з «К»
KeyValuePair<int, string> entry = default;
bool found = false;
foreach (var pair in idToName)
{
if (pair.Value.StartsWith("К"))
{
entry = pair;
found = true;
break;
}
}
if (found)
Console.WriteLine($"{entry.Key}: {entry.Value}"); // "2: Катя"
7. Корисні нюанси
Таблиця методів пошуку
| Тип колекції | Перевірити наявність Contains | Знайти індекс IndexOf | Знайти за умовою Find | Безпечний пошук за ключем TryGetValue |
|---|---|---|---|---|
|
Так | Так | Так () |
- |
(масив) |
Так (/цикл) |
Так () |
Цикл | - |
|
Так | - | Ні (тільки вручну) | - |
|
Так () |
- | Вручну за ключем/значенням | Так |
Ітеративний пошук: пишемо вручну
Для повнішого розуміння спробуйте самостійно написати метод пошуку для колекції. Наприклад, знайти індекс першого елемента, що більший за задане значення:
static int FindFirstGreaterIndex(IEnumerable<int> collection, int minValue)
{
int index = 0;
foreach (var item in collection)
{
if (item > minValue)
return index;
index++;
}
return -1; // не знайшли
}
var nums = new List<int> { 1, 4, 7, 2 };
Console.WriteLine(FindFirstGreaterIndex(nums, 3)); // 1 (число 4)
Тут ми не привʼязані ані до типу (List<T>, int[], навіть HashSet<T>), ані до внутрішньої реалізації.
Застосування пошуку у реальних завданнях
- Пошук користувача за логіном або електронною поштою в базі даних.
- Перевірка, чи є в кошику цей товар.
- Швидкий пошук налаштувань за імʼям параметра.
- Перевірка, чи вже виконано певний крок (наприклад, у workflow).
- Пошук позиції помилки в масиві логів.
На більшості співбесід із розробниками рівня Middle обов’язково ставлять запитання: «Як шукати елементи в колекції? Як би ви написали метод, який повертає перший елемент/усі елементи/індекс за умовою?». Тож практика пошуку — річ дуже корисна!
8. Типові помилки й особливості пошуку
Важливо: методи пошуку залежать від того, як порівнюють об’єкти. Наприклад, якщо до списку додавати власні класи, порівняння за замовчуванням — це порівняння посилань. Щоб пошук працював «за вмістом», потрібно перевизначити методи Equals і GetHashCode (про це детальніше в наступних лекціях).
Приклад проблеми:
var s1 = new Student { Name = "Єгор", Id = 42 };
students.Add(s1);
// Тепер створюємо новий об’єкт із тим самим ID та ім’ям:
var s2 = new Student { Name = "Єгор", Id = 42 };
Console.WriteLine(students.Contains(s2)); // False (!)
Компілятор порівнює не за полями, а за посиланням (це різні об’єкти).
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ