2010-07-20 16 views
8

Ich verstehe vollständig diese Frage wurde viel gestellt, aber ich frage nach einer bestimmten Variante und meine Suche-foo hat aufgegeben, da ich nur Algorithmen gefunden habe, die einen anhängen existierender Vektor zu einem anderen, aber nicht von einer Funktion zurückgegeben.C++ einen Vektor an einen anderen anhängen

Ich habe diese Funktion, die alle Dateien in einem Verzeichnis aufgelistet:

vector<string> scanDir(const string& dir) 

, die sich (für Unterverzeichnisse) intern nennen können.

Ich brauche eine kurze Möglichkeit, den zurückgegebenen Wert an den Vektor des Aufrufers anfügen. Ich habe in meinem Kopf so etwas wie diese (aber natürlich gibt es sich nicht :():

vector<string> fileList; 
//... 
fileList.append(scanDir(subdirname)); 

Ich fürchte, dass der Rückgabewert zu speichern und in filelist Einsetzen würde Leistung Schlechtigkeit bringt Was ich meine, das ist. :

vector<string> temp(scanDir(subdirname)); 
copy(temp.begin(), temp.end(), back_inserter(fileList)); 

Dank

PS:. von mir ich zwinge mich nicht Vektor, andere Behälter zu verwenden, die ebenso gut funktioniert und das Potential große Kopiervorgang verhindern kann, ist in Ordnung

+1

Verwandte: http: //stackoverflow.com/questions/2208293/what-is-the-most-efficient-way-to-append-one-stdvector-to-the-end-of-another – kennytm

+4

Wenn es um Leistung geht, ist die erste Frage immer : Hast du gemessen? Dann die trivialen, ist dies in einer engen Schleife, ist Ihre Anwendung Leistung kritisch? Wie wird das Kopieren eines Vektors mit dem Abrufen einer Liste von Dateien aus dem Dateisystem verglichen? –

Antwort

8

Wenn Sie in der Lage sind scanDir zu ändern, machen es zu einem (Template) Funktion eine Ausgabe Iterator akzeptieren:

template <class OutIt> 
void scanDir(const std::string& dirname, OutIt it) { 
    // ... 
    // Scan subdir 
    scanDir(subdir, it); 
    // ... 
} 

Sie müssen den zusätzlichen Vorteil der Lage sein, alle zu füllen Art von Datenstrukturen wie

std::vector<string> vector; 
scanDir(dir1, std::back_inserter(vector)); 
std::set<string> fileset 
scanDir(dir1, std::inserter(fileset, fileset.begin())); 

usw.

EDIT (siehe Kommentar ...)

Für diese Funktion für die Teilnehmer, die Initialisierung verwenden, können Sie es entweder im Konstruktor aufrufen, wie in

class MyClass { 
private: 
    std::vector<string> m_fileList; 
public: 
    MyClass(const std::string& dirname) { 
    scanDir(dirname, std::back_inserter(m_fileList); 
    } 
} 

oder unter Verwendung einer Wrapper-Funktion

std::vector<string> scanDir(const std::string& dirname) { 
    std::vector<string> result; 
    scanDir(dirname, std::back_inserter(result); 
    return result; 
} 

class MyClass { 
// Same as above.. 
    MyClass(const std::string& dirname) : m_fileList(scanDir(dirname)) { } 
} 

Ich würde die erste Version aus Gründen der Leistung (und anderer) bevorzugen ...

+0

Könnte ich eine solche Vorlage verwenden, um ein Klassendatenelement zu initialisieren (welches die Dateiliste enthält?) ... oh, warte, Ihre Kombination mit Sukru ist ziemlich mächtig (zweite Funktion, um den Rückgabewert zu erzeugen). – rubenvb

+0

Ich habe gerade einen Beitrag dazu geschrieben. Ich würde vorschlagen, die Funktion explizit im Konstruktor aufzurufen, ich kann keine Nachteile dieses Ansatzes sehen ... – MartinStettner

+0

Es wird allgemein 'OutputIterator' genannt, anstatt' InsertIterator'. –

1

Statt

vector<string> temp(scanDir(subdirname)); 

können Sie mit der Kopie

vector<string> const& temp = scanDir(subdirname); 

und gehen Sie tun:

fileList.insert(fileList.end(), temp.begin(), temp.end()); 
+0

Unwahrscheinlich, irgendeinen Unterschied zu machen, sobald der Konstruktor elision fertig ist. Es gibt keine Anforderung für die erste Codezeile, etwas zu tun, was auch die zweite Codezeile nicht benötigt. –

15

Warum nicht einfach den Vektor als Argument übergeben? Dann kann jeder Aufruf an denselben Vektor angehängt werden, ohne zu kopieren. Oder erstellen Sie eine Implementierungsklasse, die die Elemente in einem Mitgliedsobjekt akkumuliert.

+0

+1. Übergeben als Argument ist, was ich im leistungsempfindlichen Code mache. – Dummy00001

+2

Das ist 'void scanDir (const string & dir, Vektor & Ergebnis)'. – cape1232

+0

+1 für die Einfachheit. – Brian

0

Die rekursive Funktion muss alles mehrmals kopieren, O (Tiefe), um genau zu sein (d. H. Alles in der Blattebene wird immer wieder kopiert, bis es die Wurzel erreicht).

vector<string> scanDir(string path) 
{ 
    vector<string> retval; 

    scanDir(path, &retval); 

    return retval; 
} 

static void scanDir(string path, vector<string>& output) 
{ 
    .. scan 
    .. append to output 
} 
+0

In Ihrer ersten Überladung ist eine vollständige Kopie von Retval garantiert bei der Rückgabe gemacht werden. –

+0

@alexandre: Wie würde ich das vermeiden, wenn Sie diese Funktion verwenden, um ein Klassendatenelement zu initialisieren? Würde es nicht eine Compiler-Magie geben, die die Kopie vermeidet? – rubenvb

+0

@rubenvb: Rückgabewertoptimierung findet statt, wenn Sie mit einem Konstruktor zurückkehren. Verwenden Sie stattdessen eine Klasse, siehe MartinStettners Kommentar. –

8

PS:

Die beste Methode wäre dies in zwei verschiedene Funktionen werden Aufspalten Ich zwinge mich nicht zu Vektor verwendet wird, andere Behälter, die ebenso gut abschneidet und das Potenzial groß verhindern Kopiervorgang ist für mich in Ordnung.

Nun, wenn Sie einen list verwenden und rufen a.splice(a.end(), b); Sie den Kopiervorgang vollständig vermeiden. A list ist im Allgemeinen eine verkettete Liste und nicht wie bei einem vector eine Array, so dass dies eine Menge an Leistung und Auswirkungen auf die Nutzung hat. Aber Spleiß läuft in O (1), das ist ein netter Vorteil.

+0

http://stackoverflow.com/questions/1905417/array-vs-vector-vs-list – kennytm

0

Wie wäre es mit einer Hilfsfunktion?

template<class T> 
std::vector<T>& VectorAppend(std::vector<T> &target, const std::vector<T> &source) 
{ 
    size_t insertPos = target.size(); 
    target.resize(target.size() + source.size()); 
    std::copy(source.begin(), source.end(), target.begin() + insertPos); 
    return target; 
} 
+0

Was ist falsch mit einem einfachen 'std :: copy (source.begin(), source.end(), std :: back_inserter (Ziel)); 'stattdessen? Wenn Sie versuchen, die Zuweisung zu optimieren, können Sie immer 'target.reserve (target.size() + source.size());' vorher aufrufen. – sbi

2

Verwenden Sie std :: list und hängen Sie an, indem Sie std :: list :: splice verwenden.

Von the docs for splice:

Der Betrieb beinhaltet nicht den Aufbau oder die Zerstörung eines Elements Objekt und, mit Ausnahme der dritten Version wird in konstanter Zeit durchgeführt.

2
vector<string> fileList; 
vector<string> temp(scanDir(subdirname)); 

fileList.insert(fileList.end(), temp.begin(), temp.end()); 

Ich hoffe, dass Sie geholfen.

+0

Insert ist eine lineare Zeitoperation. Die Frage sagte ausdrücklich, dass dein Vorschlag nicht das ist, wonach er sucht. "Ich befürchte, dass das Speichern des Rückgabewerts und das Einfügen in die Dateiliste zu schlechter Leistung führen würde." – cape1232

+0

"Ich fürchte", für mich bedeutet das, dass er nicht sicher ist, ob dies zu Performance-Schlechtigkeit führen wird. Um ehrlich zu sein, glaube ich nicht, dass die Verwendung von insert() in diesem Fall Leistungsprobleme verursacht. Wie auch immer, danke für das Minus -, -. – virious

+0

Dies beantwortet den Titel der Frage weiß gut. Es sollte also leicht sein, diese Lösung zu finden, wenn Leute wie ich hierher kommen, um nach einer Lösung für genau das zu suchen. – kuester2000

-1

Dies ist möglicherweise nicht die einfachste mögliche Lösung, aber was ist mit C# 's StringBuilder?

Machen Sie eine list<vector<string> >, dann können Sie alle Vektoren, die Sie von Ihren Anrufen erhalten, an scanDir() an die Liste anhängen.

Wenn Sie am Ende unbedingt einen einzelnen Vektor haben müssen, können Sie, sobald Sie einen neuen Vektor erstellt haben, diesen groß genug zuweisen, damit er nicht neu skaliert werden muss, und das fertige Produkt zusammensetzen.

Alternativ können Sie eine neue Klasse machen (falls benötigt, die aus Vektor <T> ableitet) und intern verwendet die Liste < Vektor <T> > die Elemente zu speichern. Dann würden Sie Ihre Iteratoren einfach durch die Elemente in der ersten Liste durchlaufen lassen. Wenn sie das Ende erreicht haben, gehen Sie zu den Elementen in der nächsten Liste und geben nur container :: end zurück, wenn Sie das Ende der letzten Liste erreicht haben.

+0

Das ist die komplizierteste Antwort, an die ich nie denken könnte ... – rubenvb

-1

Ich weiß, dass dies Ihre Frage nicht direkt beantwortet, aber in Bezug auf Ihr zugrunde liegendes Ziel, möchten Sie vielleicht einfach Ihre Funktion in Bezug auf boost :: filesystem neu implementieren. Der Verzeichnis-Iterator ist bereits rekursiv, sodass Sie keine eigenen rekursiven Aufrufe durchführen müssen. Sie könnten einfach eine Liste in einer Schleife über den Iterator auffüllen.Es ist eine beispielhafte Implementierung von ls: http://www.boost.org/doc/libs/1_43_0/libs/filesystem/example/simple_ls.cpp

Sie erhalten auch den zusätzlichen Nutzen der (theoretischen) Plattformunabhängigkeit, relativ breite Akzeptanz (Bugs mit mehr Adoption erhalten aufgespürt schneller), etc

Verwandte Themen