Wie kann ich zwei STL-Karten zusammenführen?

73

Wie kann ich zwei STL-Karten zu einer zusammenführen? Sie haben beide die gleichen Schlüssel- und Werttypen ( map<string, string>). Bei einer Überlappung der Tasten möchte ich einer der Karten den Vorzug geben.

JonF
quelle

Antworten:

132

Angenommen, Sie möchten die Elemente in beibehalten mapAund Elemente zusammenführen, mapBfür die es keinen Schlüssel gibt mapA:

mapA.insert(mapB.begin(), mapB.end())

werde machen was du willst, denke ich.

(BEARBEITEN: Wenn Sie C ++ 17 oder neuer verwenden, berücksichtigen Sie diese Antwort: https://stackoverflow.com/a/56594603/118150 )

Arbeitsbeispiel:

#include <iostream>
#include <map>

void printIt(std::map<int,int> m) {
    for(std::map<int,int>::iterator it=m.begin();it!=m.end();++it)
        std::cout << it->first<<":"<<it->second<<" ";
    std::cout << "\n";
}

int main() {
    std::map<int,int> foo,bar;
    foo[1] = 11; foo[2] = 12; foo[3] = 13;
    bar[2] = 20; bar[3] = 30; bar[4] = 40;
    printIt(foo);
    printIt(bar);
    foo.insert(bar.begin(),bar.end());
    printIt(foo);
    return 0;
}

Ausgabe:

:!./insert
1:11 2:12 3:13
2:20 3:30 4:40
1:11 2:12 3:13 4:40
jkerian
quelle
Ich kann nicht sehen, wie das ein Duplikat in mapA nicht überschreibt, wenn die Schlüssel übereinstimmen. Wenn ich nur sage, dass mapB meine "bevorzugte" Karte war, könnte ich diese verwenden, denke ich. Auf diese Weise ist der Schlüssel in mapB derjenige, der letztendlich in der neuen Map endet (die jetzt mapA ist), wenn es sich um ein Duplikat handelt. Klingt das richtig oder verstehe ich falsch, was Insert macht, wenn es ein Duplikat gibt?
JonF
13
Beim Einfügen werden vorhandene Elemente nicht überschrieben. Wenn die Schlüssel kollidieren, hat das bereits vorhandene Element Vorrang.
David Rodríguez - Dribeas
Oh, ich verstehe. Leider baut es aber nicht. Es erzeugt eine große Fehlermeldung
JonF
2
Der Standard besagt, dass Komplexität n log (n) ist (Quelle: cppreference)
Galinette
2
@galinette Nicht ganz, es ist O (n log (n + m)), wobei n die Größe des Quellbereichs (in diesem Fall tatsächlich die Größe der Quellkarte) und m die Größe der Zielkarte ist. (Es könnte als etwas wie O (n (1 + log (m / (1 + n))) + log (m) implementiert werden, in dem speziellen Fall, dass der Quellbereich nach dem Ziel sortiert ist value_comp(), der Standard jedoch nicht Mandat das.)
Arne Vogel
34

Wenn Sie Einträge aus einer Karte auf eine andere kopieren möchten, können Sie std::map‚s insert:

targetMap.insert(sourceMap.begin(), sourceMap.end());

Beachten Sie jedoch, dass insertElemente nicht aktualisiert werden, wenn sich ihr Schlüssel bereits in targetMap befindet. Diese Elemente bleiben unverändert. Um Elemente zu überschreiben, müssen Sie explizit kopieren, z.

for(auto& it : sourceMap)
{
    targetMap[it.first] = it.second;
}

Wenn es Ihnen nichts ausmacht, die Daten zu verlieren sourceMap, können Sie insertdas Ziel auch in die Quelle und std::swapdie Ergebnisse kopieren und überschreiben :

sourceMap.insert(targetMap.begin(), targetMap.end());
std::swap(sourceMap, targetMap);

Enthält nach dem Tauschen sourceMapdie targetMapalten Daten und targetMapist eine Zusammenführung der beiden Karten, wobei die sourceMapEinträge bevorzugt werden .

Kiwibonga
quelle
18

Beachten Sie, dass es seit C ++ 17 eine merge()Methode für Karten gibt.

John Perry
quelle
10

C ++ 17

Wie in John Perrys Antwort erwähnt , bietet C ++ 17 std::map eine merge()Mitgliedsfunktion. Die merge()Funktion erzeugt das gleiche Ergebnis für die Zielkarte wie die Lösung von jkerian basierend auf der Verwendung insert(), wie Sie aus dem folgenden Beispiel sehen können, das ich von jkerian ausgeliehen habe. Ich habe gerade den Code mit einigen C ++ 11- und C ++ 17-Funktionen aktualisiert (z. B. usingTypalias , bereichsbasierte for-Schleife mit strukturierter Bindung und Listeninitialisierung ):

using mymap = std::map<int, int>;

void printIt(const mymap& m) {
    for (auto const &[k, v] : m)
        std::cout << k << ":" << v << " ";
    std::cout << std::endl;
}

int main() {
    mymap foo{ {1, 11}, {2, 12}, {3, 13} };
    mymap bar{ {2, 20}, {3, 30}, {4, 40} };
    printIt(foo);
    printIt(bar);
    foo.merge(bar);
    printIt(foo);
    return 0;
}

Ausgabe:

1:11 2:12 3:13
2:20 3:30 4:40
1:11 2:12 3:13 4:40

Wie Sie sehen können, wird merge()der Zielkarte auch Priorität eingeräumt, foowenn sich Schlüssel überschneiden. Wenn Sie es umgekehrt haben möchten, müssen Sie anrufen bar.merge(foo);.

Es gibt jedoch einen Unterschied zwischen der Verwendung insert()und der merge()Frage, was mit der Quellkarte geschieht. Die insert()Funktionen fügen der Zielkarte neue Einträge hinzu, während merge()Einträge von der Quellkarte verschoben werden. Dieses Mittel für das obige Beispiel, das insert()nicht ändert bar, aber merge()entfernt 4:40von bar, so dass nur 2:20und 3:30bleibt in bar.

Hinweis: Ich habe das Beispiel von jkerian wiederverwendet, das map<int, int>der Kürze halber verwendet wird, aber merge()auch für Sie funktioniert map<string, string>.

Code auf Coliru

hupen
quelle
3

Gemäß ISO / IEC 14882: 2003, Abschnitt 23.1.2, Tabelle 69, Ausdruck a.insert (i, j):

pre: i, j sind keine Iteratoren in a. fügt jedes Element aus dem Bereich [i, j) genau dann ein, wenn in Containern mit eindeutigen Schlüsseln kein Element mit einem Schlüssel vorhanden ist, der dem Schlüssel dieses Elements entspricht;

Da diese std :: map dieser Einschränkung folgen muss, sollten Sie sie einfügen, wenn Sie "Werte" von einer Map gegenüber einer anderen bevorzugen möchten. Zum Beispiel,

std::map<int, int> goodKeys;
std::map<int, int> betterKeys;

betterKeys.insert(goodKeys.begin(), goodKeys.end());

Wenn es also äquivalente Schlüssel in goodKeys und betterKeys gibt, bleiben die "Werte" der betterKeys erhalten.

Valentine Savchenko
quelle