Комментарии 27
Без ленивых структур данных — выглядит крайне сомнительно.
FartherThan и consecutive своим названием говорят о том, что они делают на нижнем уровне абстракции, и вникать в их реализацию не нужно, если в них нет ошибки. Это как не надо вникать, что "ходить" на самом деле есть "повторять несколько раз (шаг левой ногой, шаг правой ногой)", до тех пор пока не сломал позвоночник и не начал учиться ходить заново.
С другой стороны эту же проблему можно было бы решить нормальным наименованием переменных. Использовать не it1, it2, а cur, prev. Использовать auto, чтобы не перезагружать код. Записать географические точки в переменные с хорошим названием, чтобы было понятно. Вообще пример слишком мал для какого-то вывода. Нужно понимать в каком контексте будут использоваться эти функции и классы, оправданно ли перегрузка абстракциями или нет. Но мне бы такой код было бы читать приятнее.
int computeNumberOfBreaks(const std::vector<City> &route)
{
static const double MaxDistance = 100;
int breaks_num = 0;
auto cur = route.cbegin()
auto prev = cur++;
for ( ;cur != route.cend(); prev++, cur++)
{
auto cur_location = cur->getGeographicalAttributes().getLocation();
auto prev_location = prev->getGeographicalAttributes().getLocation();
if(cur_location.DistanceTo(prev_location) > MaxDistance)
{
breaks_num++;
}
}
return breaks_num ;
}
Ваше стремление сделать всё в одном месте понятнее в итоге привело к более длинному коду, который в ряд ли стал понятнее, и к ошибке в случае route.empty() == true.
С другой стороны, если принять во внимание возможность модификации, то дополнительные абстракции нужны. Но скорее всего не такие.
ps. Ну а длина кода вообще не аргумент.
удалил
Здесь всё упрощено для примера. Если важно потребление памяти, то можно реализовать адаптер к итератору коллекции, который при разыменовании будет возвращать смежную пару. Новый код останется почти без изменений.
int getRandomNumber()
{
return 4; // chosen by fair dice roll
// guaranteed to be random
}
Если бы ваше предложение было серебряной пулей, вы бы его увидели в книжках 80-х. Увы, наращивание уровней абстракции, в общем случае, ведёт лишь к увеличению кодовой базы. А это, в общем случае, не уменьшает энтропии.
А чаще всего код становится менее понятным, так как для ряда операций очень сложно придумать лаконичные названия, и 10 строчек превращаются в SelectEveryThirdTicketWithOddNumber, а если кодер плохо разбирается в теме или обладает плохой памятью, то в getTheThingJohnToldYouLastSunday.
И, всё-таки, не стоит переоценивать компилятор. Да, он это всё прожуёт, но жевать он будет дольше. На этапе отладки боем это очень неприятно сказывается на процесс.
Зачем вообще шаманство с итераторами, когда можно использовать старый добрый индекс?
Если вам завтра скажут «в новой версии не std::vector, а std::list», подход с индексами придется переделывать, причем на те же самые итераторы. Подход с итераторами при этом не сломается
И да, сложная формула на бумажке и её реализация в коде — разные вещи.
Особенно плохо, если новый метод не выходи понятно назвать.
Так что тут всегда трейдофф. И в данном случае до рефакторинга код был проще.
Чем плох такой, слегка изменённый вариант решения?
int computeNumberOfBreaks(const std::vector<City> &route)
{
static const double MaxDistance = 100;
int nbBreaks = 0;
for (auto cur = route.begin(), next = cur + 1;
next != route.end();
cur = next, ++next)
{
auto curLoc = cur->getGeographicalLocation().getLocation();
auto nextLoc = next->getGeographicalLocation().getLocation();
if (curLoc.distanceTo(nextLoc) > MaxDistance)
{
++nbBreaks;
}
}
return nbBreaks;
}
Такой код прост, достаточно компактный и, наконец, эффективный.
Будет ли consecutive для любых контейнеров абстракцией нулевой стоимости?
А гибкость — нужна ли она в этом случае?
ИМХО, за исключением эффективности, какой из вариантов лучше — чистая вкусовщина.
В тексте же написано, что такой код не отвечает на вопрос, что он делает, пока не просмотришь строчку за строчкой. В этом и проблема такого подхода. Такой код сложнее изменить, и он сам по себе станет ещё непонятнее, если поменять условия остановок между городами. И у вас ошибка неопределённого поведения при route.empty() == true. Чтобы мне её найти, пришлось понять весь код. При несмешивании уровней абстракций, такая ошибка не возникла бы.
Такой код сложнее изменить, и он сам по себе станет ещё непонятнее, если поменять условия остановок между городамиКонечно, если это условие станет сложнее, или понадобится в нескольких местах, его просто обязательно следует вынести в отдельную функцию.
Остаётся consecutive. Чтож, соглашусь, он действительно упростит решение, если его функционал будет использоваться в проекте многократно, если же нет — мы меняем сложность восприятия 3-х строчек (заголовка цикла for) на сложность восприятия 1-й строчки + документации к consecutive.
По поводу ошибки — да, есть такая. Хотя для её поиска не надо понимать весь код, она в самом начале :). И такая же ошибка могла бы появиться при написании consecutive. Но, да, согласен, лишь однажды, что возвращает нас к вопросу о многократности применения.
Такой код прост, достаточно компактный и, наконец, эффективный.
Чтобы понять, что делает этот код, нужно прочитать его полностью. Зная, что делают consecutive, FartherThan и count_if, код, их использующий, читается очень быстро. И меньше вероятность ошибки.
Я знаю, что начиная с C++11 функторы в основном можно заменить лямбда-функциями, но здесь нам нужно дать имя действию. Чтобы сделать это элегантно с помощью лямбда-функции, нужно немного больше работы, о чём я расскажу в отдельном посте.
int computeNumberOfBreaks(const std::vector<City> &route)
{
double MaxDistance = 100;
auto fartherThan = [=](auto &cities) {
auto & [from, to] = cities;
return from.getGeographicalAttributes().getLocation().distanceTo(
to.getGeographicalAttributes().getLocation()) > MaxDistance;
};
return count_if(consecutive(route), FartherThan(MaxDistance));
}
Итого мы дали имя действию (fartherThan) и не вводили новый класс. Возможно, «элегантные лямбды» это и сложно, но уж точно проще, чем «элегантные классы для функциональных объектов»
P.S. а вообще, дистанцию между двумя точками лучше оценивать свободной функцией
Автор, ты на верном пути.
Все циклы уже написаны и включены в стандартную библиотеку и Буст. Если задача сводится к такому уже написанному циклу, то лучше собрать решение из готовых кусков, чем каждый раз писать всё заново, вываливая на читателя тонны лапши и рискуя допустить ошибку.
Функциональщина и декларативщина стремительно врываются в плюсы. Boost.Iterator
, Boost.Range
, range-v3
не дадут соврать.
habrahabr.ru/company/nixsolutions/blog/341034
Супер-выразительный код с привлечением уровней абстракций