JavaRush /Курси /C# SELF /Пошук елементів у колекціях C#

Пошук елементів у колекціях C#

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

1. Базові можливості пошуку в колекціях

Часто слова «пошук» і «фільтрація» сприймають як синоніми, але в програмуванні це різні речі.

  • Фільтрація — це коли ми хочемо отримати всі елементи колекції, що відповідають певній умові (наприклад, усі числа, більші за 10).
  • Пошук — коли зазвичай потрібен один елемент: перший відповідний, елемент із певним значенням або просто відповідь, чи є він узагалі в колекції.

Якщо провести аналогію з бібліотекою: фільтрація — це зібрати всі книжки на тему «Космос», а пошук — запитати: «Є у вас книжка „Улісс“?» або «Де стоїть перша книжка із синьою обкладинкою?».

Усі основні колекції .NET (масиви, списки, множини, словники тощо) підтримують кілька способів пошуку елементів або перевірки наявності.

Розгляньмо найпоширеніші методи:

Колекція Перевірити наявність Знайти індекс Знайти елемент Знайти за ключем
List<T>
Contains
IndexOf
Find
-
T[]
(масив)
Contains
/
Array.IndexOf
Array.IndexOf
- -
HashSet<T>
Contains
- - -
Dictionary<TKey,V>
ContainsKey
,
ContainsValue
- - Має індексатор
[key]

Деякі методи доступні лише для конкретних типів.

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
List<T>
Так Так Так (
Find
)
-
T[]
(масив)
Так (
Array.Contains
/цикл)
Так (
Array.IndexOf
)
Цикл -
HashSet<T>
Так - Ні (тільки вручну) -
Dictionary<TKey,V>
Так (
ContainsKey
)
- Вручну за ключем/значенням Так

Ітеративний пошук: пишемо вручну

Для повнішого розуміння спробуйте самостійно написати метод пошуку для колекції. Наприклад, знайти індекс першого елемента, що більший за задане значення:

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 (!)

Компілятор порівнює не за полями, а за посиланням (це різні об’єкти).

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