C++ equivalent to Java Map getOrDefault?
Asked Answered
M

6

10

Java's getOrDefault was a nice construct for one line access to a map value or the starting point if one does not exist. I do not see anything in the map reference in C++ with a parallel. Does something exist or is it build your own?

I have objects in the map that I would update if they exist, but construct new if they do not. With getOrDefault, I could construct the object on the default side, or access it if it exists.

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

https://www.geeksforgeeks.org/hashmap-getordefaultkey-defaultvalue-method-in-java-with-examples/

Maundy answered 7/2, 2020 at 4:23 Comment(11)
Do you want to return a reference or a value?Jamiejamieson
Here is the same question #45390372 except the default is an optionalDemonism
map.at() function will throw an exception if the item isn't found. But the map [] operator inserts a default constructed value if it doesn't exist and returns that. So you probably want auto v = (m.find(k)!=m.end()?m[k]:"default") except that is ridiculously long and complicated compared to an elvis operator.Demonism
I should have said more about my intended use. I have objects in the map that I would update if they exist, but construct new if they do not. With getOrDefault, I could construct the object on the default side, or access it if it exists.Maundy
I don't ever want an optional, I either want a newly constructed object with some parameters I pass based on the current situation, or the object that was already in the map.Maundy
If you want the default constructed then just use the map [] operatorDemonism
No, the constructor for this object has parameters, unless I'm missing something about the map [] operator.Maundy
So you are looking for something like v = (m.find(k)!=m.end()?m[k]:m[k]={p1,p2}); which returns a reference to the existing or newly constructed object from the map. I don't think there is an easier way...Demonism
@JerryJeremiah That's two map traversals. insert or emplace is better.Jamiejamieson
@L.F. I wasn't really suggesting a solution - more just clarifying the question. But of course you are completely right.Demonism
Regarding Java, you are using getOrDefault in a wrong manner, if you want to insert into the map if it does not exist already then compute is the method to use.Laurellaurella
J
5

I have objects in the map that I would update if they exist, but construct new if they do not. With getOrDefault, I could construct the object on the default side, or access it if it exists.

Use emplace.

auto& element = *map.emplace(key, value).first;

emplace inserts a new element if the key is not present, and returns a pair consisting of an iterator to the element (inserted or already existent) and a bool value indicating whether insertion took place.

Jamiejamieson answered 7/2, 2020 at 5:2 Comment(3)
Thanks, that was the function I was looking for. It was staring me in the face and I missed it.Maundy
@Maundy Is this really what you want? getOrDefault is get or default, it's not get or insert-and-then-get? That is, getOrDefault never inserts into the map.Forewing
@Barry, that is absolutely true, but in this case my intention was to insert the object I constructed in the map if it did not already exist and update it if it did. Getting the reference back along with a bool indicating whether it existed allowed me to do what I want. This was my most common use case for getOrDefault anyway.Maundy
G
4

I got here when I was trying to figure out a way to solve LeetCode Single Number question using HashTable in C++.

There is no direct alternative to getOrDefault which is used in Approach 2 and Java, but I was able to use operator[] to access an element of an unordered_map. See http://www.cplusplus.com/reference/unordered_map/unordered_map/operator[]/

If the key exists in the map, then the operator[] will return the reference to its mapped value.

If the key does not exist, then the operator[] will add the key to the map with a value 0.

This could be useful when you are trying to increment a value of a key if it already exists or add a new key to the map if it does not.

For example, I used the following in C++

for (int i : nums) { hash_table[i] = hash_table[i] + 1; }

which is an alternative to the following in Java

for (int i : nums) { hash_table.put(i, hash_table.getOrDefault(i, 0) + 1); }

Glassblowing answered 19/4, 2020 at 6:19 Comment(0)
A
1

The answer of L.F is using map::emplace is the way to go if the map's value type does not require a constructor, as each time the constructor is called as shown in the example below.

C++17 comes with insert_or_assign which is slightly different.

Probably the most efficient solution is to do something like this:

template <class M, class Vp>
std::pair<typename M::iterator, bool> insert_or_create(M& map, typename M::key_type&& k, Vp&& v) {
    auto p = map.lower_bound(k);
    if (p != map.end()) {
        return std::make_pair(p, false);
    }
    return std::make_pair(map.emplace_hint(p, std::move(k), std::forward<Vp>(v)), true);
}

Here is an example that shows how it works:

#include <sstream>
#include <iostream>
#include <string>
#include <map>

class Element {
public:
    Element(int value) : value(value) {
        std::cout << "Element ctor value = " + std::to_string(value) << std::endl;
    }
    int value;
};


template <class M, class Vp>
std::pair<typename M::iterator, bool> insert_or_create(M& map, typename M::key_type&& k, Vp&& v) {
    auto p = map.lower_bound(k);
    if (p != map.end()) {
        return std::make_pair(p, false);
    }
    return std::make_pair(map.emplace_hint(p, std::move(k), std::forward<Vp>(v)), true);
}

int main(int argc, char **argv) {
    std::map<int, Element> map;

    auto& e1 = *map.emplace(1, Element(1)).first;
    std::cout << "Element in map: " << std::to_string(e1.second.value) << std::endl;
    auto& e11 = *map.emplace(1, Element(11)).first;
    std::cout << "Element in map: " << std::to_string(e11.second.value) << std::endl;

    auto e2 = *map.insert_or_assign(2, 2).first;
    std::cout << "Element in map: " << std::to_string(e2.second.value) << std::endl;
    auto e22 = *map.insert_or_assign(2, 22).first;
    std::cout << "Element in map: " << std::to_string(e22.second.value) << std::endl;

    auto e3 = *insert_or_create(map, 3, 3).first;
    std::cout << "Element in map: " << std::to_string(e3.second.value) << std::endl;

    auto e33 = *insert_or_create(map, 3, 33).first;
    std::cout << "Element in map: " << std::to_string(e33.second.value) << std::endl;
}

which produces

Element ctor value = 1
Element in map: 1
Element ctor value = 11       <-- calling ctor, not optimized away   
Element in map: 1             <-- still old value in map as expected 
Element ctor value = 2
Element in map: 2
Element ctor value = 22       <-- calling ctor  
Element in map: 22            <-- new value assigned to key 2, as expected
Element ctor value = 3
Element in map: 3             <-- ctor not called as wanted!!!!!!   
Element in map: 3

This shows that insert_or_create does not call the constructor of Element.

I am not sure why such a function is not in the std::map interface as it is pretty useful.

Aphasic answered 8/6, 2022 at 13:16 Comment(1)
Thanks for dusting this question off and adding new information. +1Maundy
L
0

Maybe I'm misunderstanding what you're asking, but that's how map works.

map<int, string> m;
string s = m[3];

will set s to a default-constructed string.

When you use operator[] to look up an key in a map, it will always hand you back an value. If the key doesn't exist in the map, it will insert it, with a default constructed value.

If you want this behavior, but with a different value (not a default-constructed value), then you could use emplace, as L.F. has suggested.

Lepore answered 7/2, 2020 at 5:11 Comment(1)
Right, I need something not default constructed. L.F.'s answer was exactly what I was looking for.Maundy
C
0
  • Java

    map.put(sum, map.getOrDefault(value, 0) + 1);

  • Equivalent code in c++

    auto it1 = map.find(value);
    if (it1 != um.end())
        map[value]++;
    else
        map[value] = 1;
Castrate answered 7/2, 2021 at 2:52 Comment(0)
G
0

I think below is what you are looking for

mymap.emplace(key, DEFAULT_VALUE).first->second = value;

like used in below sample

    #define DEFAULT_VALUE 0
    map<int,int> mymap;
    mymap[1] = 1;
    // mymap is having some entries.

    // will increment existing element.
    mymap.emplace(1,DEFAULT_VALUE).first->second++; 

    // will insert a new element with key 2 and value as default(0), than increment it.
    mymap.emplace(2,DEFAULT_VALUE).first->second++; 
Glynas answered 7/10, 2021 at 17:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.