Как избежать дублирования кода при итерации по диапазону <int>или цифре c? - PullRequest
1 голос
/ 02 февраля 2020

Мне нужно выполнить довольно сложный фрагмент кода в l oop, но l oop находится либо над вектором, либо над числовым диапазоном c целых чисел. Решение о типе l oop принимается во время выполнения:

if(!int_vector_provided){
   for(int i=0;i<N;++i){ // iterate over a numeric range
      // complex code depending on i
   }
} else {
   for(int i: int_vector){ // iterate over a vector
      // the same complex code
   }
}

Проблема заключается в том, что «сложный код» сложно реорганизовать как функцию, поскольку он зависит от многих локальных и глобальных переменных. Также нежелательно делать этот код лямбда-захватом, так как эта часть критична для производительности. Если используется диапазон чисел c, то он обычно очень велик (миллионы), поэтому очень неэффективно создавать вектор последовательных чисел такого размера.

На самом деле мне нужна пара итераторов, которую можно назначить либо началу / концу вектора, либо началу / концу диапазона чисел c. Что-то вроде:

SomeCleverIterator b,e;

if(int_vector_provided){
   b = int_vector.begin();
   e = int_vector.end();  
} else {
   // Iterators to numeric range
   // may be boost::counting_range(0,N) ???
   // but how to make boost::range iterators and 
   // to vector<int>::iterator convertible to the same type??
   b = ???;
   e = ???;
}

for(SomeCleverIterator it=b;it!=e;it++){
      // complex code
}

Я пытался играть с boost::counting_range, но его итераторы не конвертируются в vector<int>::iterator, поэтому это не помогает.

Единственный способ, которым я это вижу создать собственный шаблон класса с итераторами и создать его явно для вектора и для пары целых чисел, но это выглядит излишним для такой «тривиальной» задачи.

Есть ли лучший способ?

Ответы [ 3 ]

3 голосов
/ 02 февраля 2020

Поскольку у вас один и тот же код с разными типами, который требует шаблонов.

И так как он будет использоваться только в этой одной функции, общая c лямбда наиболее целесообразна:

auto f = [&](auto&& range) {
    for (int i : range) {
        // complex code depending on i
    }
};
if (int_vector_provided)
    f(int_vector);
else
    f(std::ranges::iota_view(0, N));

Я также использовал C ++ 20 std::ranges::iota_view.

Конечно, это также может быть случай зависти к флагу, и в этом случае вы должны просто разделить вместо этого интерфейс с двумя различными функциями, возможно, поддержанный общей реализацией.

Кроме того, глобальное состояние должно быть минимизировано, особенно изменяемый тип.

1 голос
/ 02 февраля 2020

Напоминает мне более общий вопрос Я однажды задал вопрос об итерации / генераторах. На самом деле не было никакого удовлетворительного общего удовлетворительного решения до C ++ 20. В настоящее время вы, вероятно, можете сделать это с сопрограммами, которые выдают следующий элемент и стирают их тип, но я еще не закодировал это.

В любом случае, в вашем случае проблема в коде сводится к двум основным точкам :

  1. у вас так много контекста, что вы не можете выделить функцию
  2. вы не можете иметь переменную, которая может содержать оба итератора, потому что итератор формирует вектор итератор

Вы можете выполнять итерации по обоим, используя итерацию на основе индекса, но вам придется решать на каждой итерации, используете ли вы i или v[i]. Вытащить условие, вероятно, еще быстрее.

Существует также сложное решение, заключающееся в реализации итератора-обертки, который стирает тип итератора, но если у вас уже нет чего-то подобного, Я бы посоветовал не go для этого решения, потому что оно будет сложным, трудно получить правильное и, вероятно, непропорциональным.

Вы не опубликовали контекст, поэтому я не могу сказать, в какой степени это возможно, но я чувствую, что лучший совет, который я могу вам дать, - это рефакторинг остальной части кода, прежде чем вы коснетесь этой проблемы. Вполне вероятно, что сложность контекста делает это дублирование таким болезненным, а не само дублирование. Отказ от такой проверки является обычной оптимизацией, которая не всегда наносит ущерб удобству обслуживания / читаемости.

1 голос
/ 02 февраля 2020

Проблема в том, что сложный код трудно реорганизовать как функцию, поскольку он зависит от многих локальных и глобальных переменных.

Это определенно звучит как причина, по которой вы следует реорганизовать этот код.

Также нежелательно делать этот код для захвата лямбды, поскольку эта часть критична для производительности.

Вы измерили, что захват лямбда менее производительный? Я думаю, что он должен быть примерно таким же быстрым, как и сам код, и это определенно будет самым простым решением.

При этом, я думаю, ключом к вашей проблеме является полиморфизм. Поскольку вам нужно обрабатывать только два разных типа, я бы порекомендовал посмотреть на std::variant. Вы должны использовать std:: variant<typename std::vector<int>::iterator, int> и выполнять все операции, выполняемые посетителями.

Редактировать: Низкотехнологичное решение:

int index = 0;
const int end = int_vector_provided ? int_vector.size() : N;
for(; index < end; ++index) {
    int to_use = int_vector_provided ? vector[index] : index;
    // Do calculations with to_use
}

Это добавляет посторонние проверки bool на int_vector_provided, но я думаю, что оптимизатор будет работать с этим довольно хорошо, если он равен const (я думаю, что const существенно влияет на производительность).

...