2014-02-21 3 views

ответ

4

Решение Vlad from Moscow - это рекомендуемый подход для его простоты.

«казалось бы, obious» использование стандартного алгоритма std::transform с лямбдой:

std::transform(first, last, first, [](auto elem) { 
    return test(elem) ? op(elem) : elem; 
}); 

фактически приводит к снижению производительности, поскольку все элементов будут присвоенным, а не только удовлетворяющим предикат. Чтобы изменить только предицированные элементы, потребуется также что-то вроде boost::filter_iterator, как указано в ответе киви.

Обратите внимание, что я использовал синтаксис C++ 14 с auto внутри лямбда. Для C++ 11 вам понадобится что-то вроде decltype(*first) или iterator_traits<ForwardIterator>::value_type. А в C++ 98/03 вы бы и это, и ручной объект функции.

+1

Обратите внимание, что тип умозаключение материал необходим, только если вы на самом деле параметризуя код на типе элемента контейнера , – Sneftel

+0

Как вы используете C++ 14, есть ли еще компиляторы для этого? –

+0

@ JonathanMee Clang 3.4 - это полная версия, и как Visual Studio 2013 November CTP, так и скоро выпущенная gcc 4.9 также поддерживает общие лямбды. Примечание: технически он все еще называется C++ 1y, но в 2014 году многие функции уже здесь! – TemplateRex

2
std::for_each(first, last, [](T &x) { if (test(x)) op(x); }); 

или используя подталкивание лямбда:

#include <boost/lambda/lambda.hpp> 
#include <boost/lambda/bind.hpp> 
#include <boost/lambda/if.hpp> 

std::for_each(v.begin(), v.end(), 
        if_(test())[ op() ] 
       ); 

альтернативно:

std::vector<int>::iterator it = v.begin(); 
while (it != v.end()) { 
    if (test(*it)) op(*it); 
    ++it; 
} 
+0

Да, альтернативное решение - это то, что я использую, за исключением цикла C++ 11 'for', а не' while'. Ускорение 'if' интересно, хотя я этого раньше не видел. –

7

Это может быть сделано без использования подталкивания, но применяя стандартный алгоритм std::for_each Я не советую использовать импульс для таких простые задачи. Просто глупо включать в свой проект boost, чтобы выполнить такую ​​простую задачу. Вы можете использовать boost для таких задач, если он уже включен в ваш проект.

std::for_each(first, last, [](const T &x) { if (test(x)) op(x); }); 

Или вы можете удалить Классификатор сопзЬ, если вы собираетесь изменить элементы последовательности

std::for_each(first, last, [](T &x) { if (test(x)) op(x); }); 

Иногда, когда весь диапазон последовательности используется проще использовать диапазон, основанный на утверждение вместо алгоритма becuase с использованием алгоритмов с лямбда-выражений иногда делает код менее читаемым

for (auto &x : sequence) 
{ 
    if (test(x)) op(x); 
} 

Или

for (auto &x : sequence) 
{ 
    if (test(x)) x = op(x); 
} 
3

Еще один толчок решение:

http://www.boost.org/doc/libs/1_55_0/libs/iterator/doc/filter_iterator.html

Просто позвоните станд :: преобразование вашего фильтрованной итератора.

+0

У меня есть доступ к boost, но я не вижу, как это было бы лучше, чем цикл for? –

+0

Этого не будет, это просто альтернатива. Использование алгоритма, такого как 'for_each',' transform' ... просто скроет цикл, который может быть реализован по-разному (параллельное программирование ...) – Kiwi

+0

После правильного его чтения я вижу, как это будет сделано для очень элегантное решение. Я неправильно понял, что он делал на моем первом проходе. Могу ли я использовать этот итератор прямо в stl, или мне нужно использовать алгоритм Boost? –

1

Вы хотите change_if как простой цикл?

template<typename ForwardIterator, typename UnaryPredicate> 
void change_if(ForwardIterator first, ForwardIterator last, UnaryPredicate test, UnaryOperation op) { 
    for(; first!=last; ++first) 
    if (test(*first)) *first=op(std::move(*first)); 
} 

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

Я также люблю писать контейнер на основе перегрузок:

template<typename Container, typename UnaryPredicate> 
void change_if(Container&& c, UnaryPredicate test, UnaryOperation op) { 
    for(auto& v : std::forward<Container>(c)) 
    if (test(v)) v=op(std::move(v)); 
} 

, но у меня есть это:

template<typename Iterator> 
struct range { 
    Iterator b, e; 
    Iterator begin() const { return b; } 
    Iterator end() const { return e; } 
}; 
template<typename Iterator0, typename Iterator1> 
range<typename std::decay<Iterator0>::type> make_range(Iterator0&& b, Iterator1&& e) { 
    static_assert(
    std::is_convertible< Iterator1, typename std::decay<Iterator0>::type >::value, 
    "end must be compatible with begin iterator type" 
); 
    return { std::forward<Iterator0>(b), std::forward<Iterator1>(e) }; 
} 

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

Вы увидите, что у меня есть Container на основе change_if? Это действительно диапазон change_if.

Это называется как:

change_if(myVect, [](int x){return (x%2)==0;}, [](int x){return x/2;}); 

на контейнере, а не пара итераторов. Однако, если вы хотите изменить первую половину контейнера, это не сработает: на первый взгляд, алгоритмы на основе контейнеров (ну, на основе диапазона) менее полезны.

Но make_range превращает итераторы в ряд. Таким образом, вы можете:

make_range заполняет неспособность непосредственно передать 2 итераторы диапазона на основе алгоритмов, связывая два итератора в один range<> объект. Этот угловой корпус более подробный, но типичный случай (обработки всего контейнера) становится менее подробным.

Плюс, общий вид ошибки (именование другого контейнера для begin и end) производится гораздо реже.

Все это заканчивается тем, что является эффективным, или более того, чем версия, основанная на итераторе. И, если вы замените ваши диапазоны с итерируемыми (диапазонами, которые имеют неодинаковые begin и end типов итераторов), мой change_if просто работает

+0

вернулся, чтобы прочитать некоторые невыбранные ответы на мои вопросы, и я надеялся получить некоторые пояснения к вашему ответу. Вы используете несколько вызовов, которые я не признаю их использование, из C++ 11? 1) вперед 2) распад 3) is_convertable –

+1

@jonathanmee 'forward' является условным' move', что полезно в некоторых контекстах: шаблон называется совершенной переадресацией.'decay' эмулирует, как типы затухают, когда они превращаются в« сохраненные »значения, такие как ссылки на функции указателей на функции, массивы указателей или cv, которым не присвоено, или ссылки на значения. 'is_convertable' - это признак, значение' :: value' является 'true', если один переданный ему тип может быть преобразован в другой (он является прямым). – Yakk

+0

ОК, поэтому я до сих пор не понимаю, как make_range поможет нам выборочно изменять содержимое. –

Смежные вопросы