Efficient way to move elements from a map to another
Asked Answered
C

7

11

I must find a efficient solution for this problem. I have two map. I must move some elements from map1 to map2 (namely erase from map1 and put into map2). I have the keys through which I find the elements in map1 namely i'm doing for now:

bool temp;
temp = map1[key1];
map2[key1]=temp;
map1.erase(key1)

I do it for each key(in a loop)

My question is if there is a less expensive solution of my (I use C++11 compiler)

Cubiculum answered 22/2, 2016 at 0:58 Comment(17)
well the fastest way to find an entry in a map is by using its key. What exactly are you trying to make more efficient here? Why do you need map2 at all in the first place?Heloise
@Prab: Moving data between containers is hardly an unusual thing to do!Quicksilver
@PreferenceBean I didn't say it was unusual. I was only asking the rationale behind the OP's designHeloise
If you have some criterion to decide where an element should go, I suspect you could build the two maps beforehand, without even building this to-be-split map.Nonmaterial
@Prab: I didn't say you said it was unusual.Quicksilver
@kuroineko: It's likely that elements are being moved as conditions of the application change. User input, calculation completion, anything.Quicksilver
then why puth them into a map if you will need to browse all of them to split them later? This smells either of advanced computer science or convoluted design...Nonmaterial
Simple, my algorithm requires it.(I'm doing a split of a std::map in another according to a criterion. Each map represents an entity). @PrabCubiculum
I'm working in active learning. My algorthm must learn a regular language @kuroi nekoCubiculum
Aha. Advanced computer science it is, then...Nonmaterial
Re "better way to move", you're not moving. You're copying (and erasing). Actually moving is a better way to move. ;-)Canaanite
@Umbert I'm wondering whether it might be possible to take a decision to populate a particular map rather than have one monolith map and then move its elements into other maps....Heloise
@Prab There is not a monolithic map. A map represents a state (of DFA) and when the algorithm discovers that there is a prefix not equivalent to others i must create a new map (a new state) with this prefix (and the others that have the same behavior). Then there is a map for each state (found up to that point).Cubiculum
@Umbert I understand a new prefix results in a new state. But does the prefix decide what keys will be present in every map?Heloise
@Prab Practically all prefixes for each suffix must have the same values (values derived from a Membership query to a DFA teacher the so-called oracle, practically the DFA that must be inferred). If a prefix has not this property you must split and create a new state (a new map)Cubiculum
@Umbert I don't have first hand access to your problem domain so you have to judge what's best for you. But my suggestion is that if there's a way to judge if the map entry shouldn't be in that map in the first place then go for it rather than move around entries. This being a DFA I would expect that to be possible....Heloise
Does this answer your question? Can I move-assign a std::map's contents into another std::map?Rubierubiginous
C
15

The ideal solution has only become possible with C++17: map::extract()

Std::map is typically implemented as a binary tree, and a binary tree stores one key-value entry per memory object. That makes it possible to move an entry by simply exchanging the ownership of that piece of memory. This is done by just flipping some internal pointers (including rebalancing the two trees, which has to be done anyway if the end result is to have changed both trees).

This avoids not only moving the object around in memory, but any memory allocation and deallocation (which has unpredictable worst case performance).

Example:

// Before Darwin, it was thought that whales were fish.
std::map<int, std::string> fish{{1,"whaleshark"}, {2,"whale"}, {3,"shark"}};
std::map<int, std::string> mammalia{{1,"cat"}, {2,"dog"}};

// Oops, the whale belongs to mammalia.
auto whale = fish.extract(2);
whale.key() = 3;
mammalia.insert(std::move(whale));

Before C++17, you had to implement your own map in order to do this.

Carlettacarley answered 27/9, 2018 at 16:15 Comment(3)
How does one actually get the value of the node returned from extract? Something along the lines of std::string whale = std::move(fish.extract(2));.Cuthbertson
@Cuthbertson based on the docs for node_handle you should be able to do std::string whale = std::move(fish.extract(2).mapped());Standstill
Perfect, appreciate it. I have yet to ascend to the levels of C++ that grant any useful parsing of STL docs beyond the individual written words.Cuthbertson
H
1

std::map documentation : http://www.cplusplus.com/reference/map/map/

for (auto key : keys) // keys of elements to move
{
    try {
        map2[key] = std::move(map1.at(key)); // here is the C++ 11 optimization you looked for
        map1.erase(key);
    }

    // handle error if map1 does not store any element with current key
    catch (std::out_of_range & ex) {
        // TODO handle error
    }
}
Hartzel answered 22/2, 2016 at 1:26 Comment(2)
Upvoted for moving, although construction + assignment through use of indexing notation, is clear instead of efficient (the question is about efficiency).Canaanite
@Hartzel Thank you. I will use this solution although I have not 100% clear what makes move (If I understand it avoids the unnecessary copies)Cubiculum
U
1

you can do this with std::map's extract function come with c++17.

here is the explanation and examples https://en.cppreference.com/w/cpp/container/map/extract

Ullman answered 29/9, 2022 at 7:1 Comment(1)
Welcome to SO. While the link you provided may be helpful, link-only answers are discouraged for various reasons. Please edit to include the relevant information directly in your answer and add some context and explanation. You can find more information on how to write good answers in the help center.Fescennine
Q
0

Well your temporary variable is pointless; just write:

map2[key1] = map1[key1];
map1.erase(key1);

… though your compiler was already likely to make that "optimisation" for you.

Concerning the value itself, a bool is already about as atomic as it gets — there are no magic tricks that can make copying one any faster.

You can (as long as map1[key1] assuredly already exists) is to eliminate one of the map lookups and a zero-initialisation:

auto it = map1.find(key1);
assert(it != map1.end());

map2.insert(std::make_pair(key1, it->second));
map1.erase(it);

But look how less expressive this is! Don't do it unless you really have a performance problem.

Quicksilver answered 22/2, 2016 at 1:3 Comment(0)
P
0

If the value_type has a default constructor, there's a good thing in the STL named std::swap that you don't need C++11 move semantic.

std::swap(map1[key], map2[key]);
map1.erase(key);

In your case, I think it's find just to copy the bool value. But if what slows your program is copying the key_type I'm afraid there seems no way but to use both maps without moving entries between them, because the key_type is always const qualified so that not movable.

Pectase answered 22/2, 2016 at 1:26 Comment(0)
S
0

Insert the entire map through iterators, and then clear the original.

map2.insert(map1.begin(), map1.end());
map1.clear();
Sonja answered 22/2, 2016 at 1:36 Comment(0)
G
0

Based on @RCYR's answer, I would propose the following:

auto& itToRemove = map1.find(key); //perform the search in map1 only once
map2.emplace( key, std::move(itToRemove->second); //map2 entry is init with the map1 one without default initialization
map1.erase(itToRemove ); //go straight to the map1 entry an remove it.

Basically, I removed one search in the map for each entry deleted and a default initialization of the map2 entry.

Gillie answered 14/7, 2023 at 8:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.