Мы с вами уже давно обсуждаем матричное или numpy-программирование. Для удобства привожу список статей:
- Numpy для продвинутых
- Продолжаем numpy-программирование для продвинутых
- Создаем скользящее окно без циклов
- Перебор значений без циклов
- Поиск локального максимума с помощью numpy-программирования
- Numpy для работы с координатами прямоугольников
В начале прошлой статьи был затронут вопрос, который сегодня мы разберем. Для этого возьмем последнюю задачу из предыдущей статьи и попробуем решить её без библиотеки numpy. Если в предыдущей статье мы искали все индексы, то сейчас будем искать первый индекс прямоугольника, координаты которого близки не более чем на 5%.
Всю задачу можно решить в одну строку, но так будет хуже читаться, да и статья окажется короткой. Воспользуемся функциональным программированием, которое предлагает одну задачу разбивать на подзадачи, а каждую подзадачу оборачивать в функцию. Для начала напишем функцию определения близки ли значения:
В неё передаются два дробных числа для сравнения и параметр относительного сходства, которому присвоено значение по умолчанию 0.05. Чтобы определить насколько два числа близки, мы должны от первого отнять второе, поделить на среднее от суммы двух чисел. Для удобства округлим до 3 знаков после запятой, результат попадает в функцию abs, так как знак нас не интересует. Функция возвращает булево значение, поэтому сравним полученное значение с допуском, оно должно быть меньше или равно. Мы могли бы воспользоваться одноименной функцией из библиотеки math, но раз уж мы отказались от numpy, то зачем пользоваться math?
А теперь самое интересное: как мы будем обходиться без циклов в подзадаче, где используются итерации? Для этого используют функцию map:
Функция на первый взгляд может показаться запутанной. Начнем же её "распутывать". Функция list возвращает список, а у списка есть функция index, которая выводит первое совпадение в списке с параметром (True). Первая функция map передает в лямбда-функцию список rects в виде строк, как параметр с (Це), которая попадает во вторую map, использующую функцию isclose уже как отдельное дробное число. Точно так же в эту же map передается и другой список pattern в виде чисел. Результат второй map оборачивается в виде списка второй функцией list и попадает в функцию all, которая возвращает либо True, если все значения списка подходят под True, либо False во всех остальных случаях. Таким образом, в первую функцию list попадает список булевых значений, соответствующий строкам списка rects.
Если вам это объяснение вам не понятно, то следует разложить строку на подстроки или по нескольким функциям (функциональное программирование). Если после разложения остается непонятным какой-то участок кода, то начинаем дебажить, то есть вызывать каждую функцию по отдельности, передавая в неё параметры, и анализируя выходные значения.
Если же взять в целом, то без numpy нужно постоянно "изобретать велосипед", то есть в вместо функций numpy мы пишем свой код, который будет работать медленнее, да и вероятность ошибок в нем выше. Кроме того, списки не позволяют выполнять арифметические операции над матрицей, поэтому арифметика выполняется с каждым элементом. То есть, нет никакого выигрыша от неиспользования numpy. Другой вопрос, если используете конкурентов, например, тензоры от tensorflow или pytorch, тогда можно обойтись и без numpy. Но это уже другая тема.