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
Массивы не имеют методов Find внутри себя (как List<T>), но для них есть статический класс Array:
int[] numbers = { 1, 2, 3, 2, 4 };
int pos = Array.IndexOf(numbers, 2); // 1 — первый индекс 2-ки
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>), ни ко внутренней реализации.
Применение поиска в реальных задачах
- Поиск пользователя по логину или e-mail в базе данных.
- Проверка, есть ли в корзине покупок данный товар.
- Быстрый поиск настроек по имени параметра.
- Проверка, был ли уже выполнен определённый шаг (например, в 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 (!)
Компилятор сравнивает не по полям, а по ссылке (это разные объекты).
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ