2016-05-19 45 views
15

C'è un modo più semplice per scrivere questo, ad es. usando un algoritmo STL o boost?Algoritmo C++ per applicare una funzione a elementi consecutivi

std::vector<int> v { 0, 1, 2, 3 }; // any generic STL container 
std::vector<int> result; 
std::transform(v.begin(), v.end() - 1, // (0, 1, 2) 
       v.begin() + 1,   // (1, 2, 3) 
       std::back_inserter(result), 
       [](int a, int b){ return a + b; }); // any binary function 
// result == { 1, 3, 5 } 
+1

@erip Iteratori e iteratori inversi non sono tipi compatibili. 'v.end() - 1' e' v.rbegin() 'puntano allo stesso elemento, ma non possono essere confrontati. – interjay

+0

@interjay Whoops. :) – erip

+1

Sembra che una discussione senza risposta sia già stata presentata qui: http://stackoverflow.com/questions/19927563/for-each-that-gives-two-or-nadadacent-elements Inoltre, sono sicuro di aver visto un post sul blog con un'implementazione, lo posterò come risposta se riesco a trovarlo ... –

risposta

9

È possibile utilizzare il binary version of std::transform.

Gli algoritmi std::adjacent_find/std::adjacent_difference possono essere abusati.

+1

La differenza con il mio algoritmo è che 'adiacente_differenza' copia il primo elemento in la sequenza di output non modificata. Questo è in realtà abbastanza stupido perché introduce la necessità di avere tipi di input e output compatibili.:/ –

10

propongo di utilizzare un ciclo for:

for(std::vector::size_type i = 0; i < v.size() - 1; i++) 
    result.push_back(v[i] + v[i+1]) 

Un ciclo più generico per iteratori bidirezionali:

// let begin and end be iterators to corresponding position 
// let out be an output iterator 
// let fun be a binary function 
for (auto it = begin, end_it = std::prev(end); it != end_it; ++it) 
    *out++ = fun(*it, *std::next(it)); 

Siamo in grado di andare un po 'oltre e scrivere un ciclo per gli iteratori in avanti:

if(begin != end) { 
    for (auto curr = begin, 
     nxt = std::next(begin); nxt != end; ++curr, ++nxt) { 
     *out++ = fun(*curr, *nxt); 
    } 
} 

Infine, e l'algoritmo iteratori di input. Tuttavia, questo richiede che il tipo di valore sia copiabile.

if(begin != end) { 
    auto left = *begin; 
    for (auto it = std::next(begin); it != end; ++it) { 
     auto right = *it; 
     *out++ = fun(left, right); 
     left = right; 
    } 
} 
+2

@FelixDombek Ho aggiunto un ciclo per gli iteratori non casuali (non più così carino). – user2079303

+0

'v.end() - 1' richiederebbe anche l'accesso casuale, quindi dovresti usare' std :: prev' invece. – interjay

+0

@interjay giusto sei. Anche se non funzionerà con gli iteratori forward :( – user2079303

1

Scriverò il proprio algoritmo per applicare un funtore a ciascuna coppia di elementi nel contenitore.

(blurb sfacciato) Nella mia presentazione ACCU quest'anno, "STL Algorithms – How to Use Them and How to Write Your Own", ha mostrato come scriverne uno come questo. L'ho chiamato adjacent_pair (circa 25:00 nel video)

template <typename ForwardIterator, typename Func> 
void adjacent_pair(ForwardIterator first, ForwardIterator last, Func f) 
{ 
    if (first != last) 
    { 
     ForwardIterator trailer = first; 
     ++first; 
     for (; first != last; ++first, ++trailer) 
      f(*trailer, *first); 
    } 
} 
+1

Beh, se ti piacerebbe davvero scrivere il mio algoritmo, non ti fermerò: o) –

1

std::adjacent_difference è proprio per questo, ma come lei ha ricordato, copia il primo elemento al risultato, che non si vuole. Utilizzando Boost.Iterator, è abbastanza facile creare un back_inserter che getta via il primo elemento.

#include <boost/function_output_iterator.hpp> 

template <class Container> 
auto mybackinsrtr(Container& cont) { 
    // Throw away the first element 
    return boost::make_function_output_iterator(
      [&cont](auto i) -> void { 
       static bool first = true; 
       if (first) 
       first = false; 
       else 
       cont.push_back(i); 
      }); 
} 

Poi si può #include <boost/range/numeric.hpp> e fare questo:

std::vector<int> v { 0, 1, 2, 3 }; // any generic STL container 
std::vector<int> result; 
boost::adjacent_difference(v, mybackinsrtr(result), std::plus<>{}); // any binary function 

See it on ideone


Quando si desidera la funzione binaria per restituire un tipo diverso (ad esempio una stringa), quanto sopra soluzione non funzionerà perché, anche se l'inserimento cont.push_back(i) non è mai chiamato per il primo eleme copiato nt, deve essere ancora compilato e non funzionerà.

Quindi, è possibile creare un back_inserter che ignora qualsiasi elemento di un tipo diverso da quello nel contenitore. Questo ignorerà il primo, copiato, elemento, e accetterà il resto.

template <class Container> 
struct ignore_insert { 
    // Ignore any insertions that don't match container's type 
    Container& cont; 
    ignore_insert(Container& c) : cont(c) {} 
    void operator() (typename Container::value_type i) { 
     cont.push_back(i); 
    } 
    template <typename T> 
    void operator() (T) {} 
}; 

template <class Container> 
auto ignoreinsrtr(Container& cont) { 
    return boost::make_function_output_iterator(ignore_insert<Container>{cont}); 
} 

Quindi è possibile utilizzarlo in modo simile.

std::vector<int> v { 0, 1, 2, 3 }; // any generic STL container 
std::vector<std::string> result; 
boost::adjacent_difference(v, ignoreinsrtr(result), [](int a, int b){ return std::to_string(a+b); }); 

On ideone

+0

Bello, ma ancora non funziona per un functor che cambierebbe il tipo di iteratore ('[] (int a, int b) {std :: to_string (a + b);} ') tuttavia questa funzionalità potrebbe essere inserita nell'output dell'output, eliminando la necessità di qualsiasi functor qui e si potrebbe semplicemente usare' std :: copy'. –

+0

@FelixDombek: non sono sicuro del perché non funzioni. Puoi affrontare quel caso particolare [come ho fatto qui] (http://ideone.com/OOaDKP), sostituendo l'inseritore posteriore lambda con una struttura che ignora 'int's e inserisce' stringa's. – Kundor

+0

Non funziona con 'adiacente_differenza' perché copia il primo elemento nell'intervallo di risultati invece di applicare il functor. Quindi, poiché l'iteratore dei risultati deve essere in grado di accettare un elemento del tipo di origine, il funtore non può avere alcun tipo di ritorno incompatibile. –