Обобщение двух методов - PullRequest
0 голосов
/ 01 июля 2018

Рассмотрим структуру:

struct B {int b1, b2;};

struct A {
  std::set<B, compBLess> m_setLess;
  std::set<B, compBGreater> m_setGreater;

  void onFirst(int val) {
    auto i_set = m_setLess.begin();
    auto comp = [&](){ return val >= i_set->b1; };

    while ( i_set != m_setLess.end() && comp() ) {
      sameForBoth(*i_set);
      ++i_set;
    }
  }

  void onSecond(int val) {
    auto i_set = m_setGreater.begin();
    auto comp = [&](){ return val <= i_set->b1; };

    while ( i_set != m_setGreater.end() && comp() ) {
      sameForBoth(*i_set);
      ++i_set;
    }
  }

  void sameForBoth() {}
};

Возможно ли рефакторинг методов onFirst и onSecond в один лаконичный код без ущерба для обслуживания кода? Обратите внимание, что compBLess / compBGreater нельзя использовать вместо comp.

Мой взгляд на проблему:

  template<typename TSet>
  void onBoth(int val){
    TSet* set;
    if ( std::is_same<TSet, decltype(m_setLess)>::value ) {
      set = reinterpret_cast<TSet*>(&m_setLess);
    } else {
      set = reinterpret_cast<TSet*>(&m_setGreater);
    }

    auto i_set = set->begin();

    std::function<bool()> comp;
    if( std::is_same<TSet, decltype(m_setLess)>::value )
      comp = std::function<bool()>([&]() { return val >= i_set->b1; });
    else
      comp = std::function<bool()>([&]() { return val <= i_set->b1; });

    while ( i_set != set->end() && comp() ) {
      sameForBoth(*i_set);
      ++i_set;
    }
  }

Но мое решение кажется слишком сложным. Также я не уверен, что использование reinterpret_cast<> таким способом является хорошей практикой.

Есть ли другой способ?

Ответы [ 2 ]

0 голосов
/ 01 июля 2018

Если я правильно понимаю, кажется, что вы хотите применить действие к каждому элементу, который удовлетворяет унарному предикату comp. Следовательно, мы можем сканировать элементы линейно и применять функцию до тех пор, пока не будет выполнен данный предикат.

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

template <typename I, typename P, typename F>
// I models InputIterator
// P models UnaryPredicate
// F models UnaryFunction
// Domain<P> == ValueType<I>
// Domain<F> == ValueType<I>
void apply_until(I first, I last, P p, F f) {
  while (first != last) {
    if (!p(*first)) return;
    f(*first);
    ++first;
  }
}

Мы можем использовать такой общий алгоритм, например, для распечатки значений в диапазоне, который оказывается строго меньше чем 3:

int main() {
  std::set<int> s1 = {1, 2, 3, 4, 5};
  apply_until(s1.begin(), s1.end(), [](int x) { return x < 3;}, [](int x) { std::cout << x << '\n'; });
}

Я бы сохранил различие между onFirst и onSecond, так как они предназначены для работы на разных наборах. Код для вашего варианта использования может выглядеть примерно так:

void onFirst(int val) {
    apply_until(m_setLess.begin(), m_setLess.end(), [&](B const& x) { return x.b1 >= val; }, [&](B const& x) { sameForBoth(x); });
}

void onSecond(int val) {
    apply_until(m_setGreater.begin(), m_setGreater.end(), [&](B const& x) { return x.b1 <= val; }, [&](B const& x) { sameForBoth(x); });
}
0 голосов
/ 01 июля 2018

В обеих функциях мы выполняем итерацию в диапазоне от начала std::set до заданного итератора, который зависит от входного значения.

В этом ответе я предполагаю, что compBLess и compBGreater определены следующим образом (важная часть заключается в том, что поле b1 преобладает над b2. И посмотрите в конце несколько отличную версию)

struct compBLess {
  bool operator ()(B const & o1, B const& o2) const {
      return std::make_pair(o1.b1,o1.b2) < std::make_pair(o2.b1,o2.b2);
  }
};
struct compBGreater {
  bool operator ()(B const & o1, B const& o2) const {
      return std::make_pair(o1.b1,o1.b2) > std::make_pair(o2.b1,o2.b2);
  }
};

В этом предположении, я думаю, что идиоматический способ сделать это состоит в том, чтобы использовать нижний, верхний методы std :: set , чтобы найти конец итерации, а затем использовать

template<typename Iterator>
void foo(Iterator it, Iterator end) {
    std::for_each(it,end,[this](auto & o){ sameForBoth(o); });
}

Это будет более эффективным с точки зрения производительности, потому что мы будем делать O (log (size_of_set)) сравнений (используя нижний / верхний) вместо O (size_of_set) сравнений (используя comp в цикле)

Фактическая реализация других методов выглядит следующим образом:

void onFirst(int val) {
    foo(m_setLess.begin(), m_setLess.lowerbound({val,std::numeric_limits<int>::min}));
}
void onSecond(int val) {
    foo(m_setGreater.begin(), m_setGreater.upperbound({val-1,std::numeric_limits<int>::max}));
}

Редактировать: После комментария @ z3dd, вот реализация, которая работает для слегка отличающихся compBLess и compBGreater (упорядочение по сравнению с полем b2 обращено):

struct compBLess {
  bool operator ()(B const & o1, B const& o2) const {
      return std::make_pair(o1.b1,-o1.b2) < std::make_pair(o2.b1,-o2.b2);
  }
};
struct compBGreater {
  bool operator ()(B const & o1, B const& o2) const {
      return std::make_pair(o1.b1,-o1.b2) > std::make_pair(o2.b1,-o2.b2);
  }
};

void onFirst(int val) {
    foo(m_setLess.begin(), m_setLess.lowerbound({val,std::numeric_limits<int>::max}));
}
void onSecond(int val) {
    foo(m_setGreater.begin(), m_setGreater.upperbound({val-1,std::numeric_limits<int>::min}));
}

[Обратите внимание, что если compBLess и compBGreater не реализованы так, как любая из двух приведенных реализаций, то ответ @Ilio Catallo - тот, который нужно использовать.]

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...