2010-07-08 6 views
10

Lassen Sie uns sagen, ich habe folgendes Objekt:Was ist der beste Weg, um das Ergebnis einer Elementfunktion für alle Elemente in einem Container zu summieren?

struct Foo 
{ 
    int size() { return 2; } 
}; 

Was ist der beste Weg, (die meisten wartbar, lesbar, etc.) in einem vector<Foo> die Gesamt size aller Objekte zu bekommen? Ich werde meine Lösung posten, aber ich bin an besseren Ideen interessiert.

Update:

Bisher haben wir:

  • std :: akkumulieren und ein Funktor
  • std :: akkumulieren und ein Lambda-Ausdruck
  • plain ol‘for-Schleife

Gibt es noch andere praktikable Lösungen? Können Sie mit boost::bind oder std::bind1st/2nd etwas Wartbares machen?

+3

'std :: vector vec verwendet; vec.size() * 2', da wir wissen, dass 'Foo :: size' immer 2 zurückgibt. :) – jalf

Antwort

23

Zusätzlich zu Ihrem eigenen Vorschlag, wenn Ihr Compiler C++ 0x Lambda-Ausdrücke unterstützt, können Sie diese kürzere Version verwenden:

std::vector<Foo> vf; 

// do something to populate vf 


int totalSize = std::accumulate(vf.begin(), 
           vf.end(), 
           0, 
           [](int sum, const Foo& elem){ return sum + elem.size();}); 
+0

Tippfehler: Am Ende des Körpers des Lambda fehlt ein Semikolon (ich kann mich nicht selbst bearbeiten). – rafak

7

Verwenden Sie std::accumulate und einen Funktor.

#include <functional> 
#include <numeric> 

struct SumSizes : public std::binary_function<int, Foo, int> 
{ 
    int operator()(int total, const Foo& elem) const 
    { 
     return total + elem.size(); 
    } 
}; 

std::vector<Foo> vf; 

// do something to populate vf 

int totalSize = std::accumulate(vf.begin(), 
           vf.end(), 
           0, 
           SumSizes()); 
+0

Ihre Lösung ist natürlich die idiomatische, aber eine einfache Iteratorschleife könnte in solchen einfachen Fällen einfacher sein. – Philipp

+0

+1 Dies würde durch Templatierung von 'SumSizes' für die Generizität verbessert, da alle Standardcontainer eine 'size()' -Memberfunktion haben. –

+0

@Jon, ich denke, Sie haben die Frage vielleicht missverstanden. Der Punkt war nicht, die Größe des Containers zu erhalten, sondern das Ergebnis einer Elementfunktion aller Elemente zu summieren. Vielleicht war "Größe" ein schlechter Name für eine solche Funktion. –

4

Hier ist die down-to-earth-Lösung:

typedef std::vector<Foo> FooVector; 
FooVector vf; 
int totalSize = 0; 
for (FooVector::const_iterator it = vf.begin(); it != vf.end(); ++it) { 
    totalSize += it->size(); 
} 
+0

So viel einfacher zu lesen als die anderen, funktionalen Lösungen. – Jon

7

Ich finde Booster Iteratoren elegants, obwohl sie ein wenig wortreich sein können (range-based Algorithmen würden dies besser machen). In diesem Fall transform iterators kann die Arbeit erledigen:

#include <boost/iterator/transform_iterator.hpp> 
//... 

int totalSize = std::accumulate(
    boost::make_transform_iterator(vf.begin(), std::mem_fn(&Foo::size)), 
    boost::make_transform_iterator(vf.end(), std::mem_fn(&Foo::size)),0); 

Edit: ersetzt "boost::bind(&Foo::size,_1)" durch "std::mem_fn(&Foo::size)"

Edit: Ich fand nur, dass die Boost.Range Bibliothek wurde aktualisiert Bereich Algorithmen einzuführen! Hier ist eine neue Version der gleichen Lösung:

#include <boost/range/distance.hpp> // numeric.hpp needs it (a bug?) 
#include <boost/range/numeric.hpp> // accumulate 
#include <boost/range/adaptor/transformed.hpp> // transformed 
//... 
int totalSize = boost::accumulate(
    vf | boost::adaptors::transformed(std::mem_fn(Foo::size)), 0); 

Hinweis: Die Leistungen sind in etwa gleich (siehe meinen Kommentar): intern, transformed verwendet transorm_iterator.

+1

Ich habe Timings verglichen diese Lösung und die direkte, und leider ist diese langsamer (ich fand einen Faktor zwischen 2 und 5). Dies ist jedoch möglicherweise kein Problem. – rafak

+0

Ich denke, das ist die beste Antwort. Das Problem ist ** was ** zu akkumulieren, das von einem benutzerdefinierten Iterator angesprochen wird, nicht ** wie ** zu akkumulieren, das mit einem Funktor angesprochen wird. Das Standardakkumulationsverhalten (plus) _ist_ was du willst. Erwägen Sie, dieses Problem auf das innere Produkt auszuweiten: Der transformierte Iterator ist wiederverwendbar, während der Funktor nicht wiederverwendbar ist. Ein neuer Funktor für jeden Algorithmus wäre erforderlich, um das Standardverhalten in Bezug auf die Elementgröße einfach neu zu definieren(). –

4

C++ 11 (und darüber hinaus) bereichsbasierte for-Schleife

std::vector<Foo> vFoo; 
// populate vFoo with some values... 
int totalSize = 0; 
for (const auto& element: vFoo) { 
    totalSize += element.size(); 
}