How to control and modify the std::map ordering using a user-defined key - c++

I started out by using a std::string as my map key, as each item in my map can be uniquely identified by a string alone.
Then I realised that it would be a lot more useful to me to have the map ordered in a certain way, based on another parameter, so I added an int called priority to my key to help with ordering. The idea is that I iterate over the map and process the higher priority items first. I now have the following user-defined struct as my map key :
struct MyKey {
// key data
std::string addr;
int priority;
// constructor
MyKey(const std::string & s, const int p)
: addr(s), priority(p) {}
// overloaded operator
bool operator<(const MyKey &that) const {
// same key if addr is the same
if (that->addr == this.addr)
return false;
// not same key so look at priorities to determine order
if (that.priority < this->priority)
return true;
if (that.priority > this->priority)
return false;
// priorities are the same so use the string compare
return (that.addr > this->addr);
}
};
The map ordering appears to be working correctly, and when new items are added they are entered at the expected position automatically if you were to iterate over the map. For instance for a map of std::string values:
std::map<myKey, std::string> myMap;
myKey key1 = myKey(std::string("key1"), 1);
myKey key2 = myKey(std::string("key2"), 2);
myKey key3 = myKey(std::string("key3"), 3);
myKey key4 = myKey(std::string("key4"), 4);
myMap[key1] = std::string("value1");
myMap[key2] = std::string("value2");
myMap[key3] = std::string("value3");
myMap[key4] = std::string("value4");
Would result in the following map key-value pairs at respective indexes:
[0] { addr = "key4", priority = 4 }, { "value4" }
[1] { addr = "key3", priority = 3 }, { "value3" }
[2] { addr = "key2", priority = 2 }, { "value2" }
[3] { addr = "key1", priority = 1 }, { "value1" }
However...I am having problems when it comes to modifying an existing priority of a key that is already present in the map.
In this situation, find() and [] (with respect to std::map) don't work as I want them to:
myKey modified_key1 = myKey(std::string("key1"), 5);
// problem 1 - this does not return iterator to "key1",
// but instead to end of the map
auto & foundKey = myMap.find(modified_key1);
// problem 2 - this adds a brand new item to the map
myMap[modified_key1] = std::string("value1");
After problem 2 as mentioned above, I am getting a new item added to the map with the same addr of an existing item. The new item appears to be added in the expected position based on the new (modified) priority, but the existing item to be updated remains as it was. So I end up with 2 items in the map with the same addr in their keys:
[0] { addr = "key1", priority = 5 }, { "value1" }
[1] { addr = "key4", priority = 4 }, { "value4" }
[2] { addr = "key3", priority = 3 }, { "value3" }
[3] { addr = "key2", priority = 2 }, { "value2" }
[4] { addr = "key1", priority = 1 }, { "value1" }
This is a problem for me as I would like to still rely on the notion that the addr of the map item key is unique.
What I want is the map to realise it already has an item with the same key (or more to the point the same key addr) and to re-order the item accordingly.
I have tried experimenting with compare functors as part of the map definition, and also overloading the keys == operator, but the same problem persists.
What am I missing or should I be approaching this differently?

The problem is that your comparison operator implemented incorrectly, it does not provide strict weak ordering hence undefined behavior of the std::map, lets say you have 3 objects of MyKey:
MyKey mk1{ "a",3 }, mk2{ "b", 2 }, mk3 { "a", 1 };
mk1 < mk2 -> true as 3 > 2
mk2 < mk3 -> true as 2 > 1
mk1 < mk3 -> false as addr is the same, but must be true
live example
I do not think your problem is easily solvable with std::map. Possible solution is to use boost::multi_index with address as one index and priority as another. To change priority of existing element boost::multi_index provides method to replace data.

Instead of MyKey you can use std::tuple<int, std::string>, it defines the relational operators for you:
using MyKey = std::tuple<int, std::string>;
Saves you a dozen of lines.
You cannot modify keys of elements in any associative containers. Instead, you need to remove the element using the old key and re-insert it with a new key.

Related

Using List as a key in map in Dart

Here is my Dart code
var mp = new Map();
mp[[1,2]] = "Hi";
mp[[3,5]] = "sir";
mp.remove([3,5]);
print(mp);
Output here is null
How can i access value at mp[[3,5]]?
Two list instances containing the same elements is not equal to each other in Dart. This is the reason your example does not work.
If you want to create a Map which works like your example, you can use LinkedHashMap from dart:collection (basically the same when you are using Map()) to create an instance with its own definition of what it means for keys to be equal and how hashCode is calculated for a key.
So something like this if you want to have keys to be equal if the list contains the same elements in the same order. It should be noted it does not support nested lists:
import 'dart:collection';
void main() {
final mp = LinkedHashMap<List<int>, String>(
equals: (list1, list2) {
if (list1.length != list2.length) {
return false;
}
for (var i = 0; i < list1.length; i++) {
if (list1[i] != list2[i]) {
return false;
}
}
return true;
},
hashCode: Object.hashAll,
);
mp[[1, 2]] = "Hi";
mp[[3, 5]] = "sir";
mp.remove([3, 5]);
print(mp); // {[1, 2]: Hi}
}
I should also add that this is really an inefficient way to do use maps and I am highly recommend to never use List as keys in maps.
You add a list instance as a key to the Map object. You need the corresponding list instance to delete it again.
There are two ways to access
First;
final mp = {};
mp[[1,2]] = "Hi";
mp[[3,5]] = "sir";
mp.removeWhere((key, value) {
if(key is List){
return key.first == 3 && key[1] == 5;
}
return false;
});
Second;
final mp = {};
final key = [3, 5];
mp[[1,2]] = "Hi";
mp[key] = "sir";
mp.remove(key);
Map countries = {
"01": "USA",
"02": "United Kingdom",
"03": "China",
"04": "India",
"05": "Brazil",
"06": "Nepal",
"07": "Russia"
};
//method 1:
var _key = countries.keys.firstWhere((k)
=> countries[k] == 'Russia', orElse: () => null);
print(key); //output: 07

How do I increment value of an entry if it already exists in the PQ?

I'm using this question as a starting point:
Add Key and Value into an Priority Queue and Sort by Key in Java
I have a similar situation, where I have a PQ of my POJO that has two important attributes: key and value.
However, before I add a new entry into the PQ, first I want to check if an entry with the same key exists already in the PQ. In that case, I want to increment its value instead. How do I go about doing this?
The javadoc says that.
It is strongly recommended, but not strictly required that (x.compareTo(y)==0) == (x.equals(y))
But again, this is not necessary. What it is necessary is to override the method public boolean equals(Object entry) for the method contains() to work, instead you are declaring the method public boolean equals(Entry entry).
You should have an equals() similar to.
#Override
public boolean equals(Object entry) {
if (entry instanceof Entry) return this.key == ((Entry) entry).key;
else return false;
}
Another thing to consider it that it is a terrible idea to mutate the object when this is already in a sorted/hashed collection. This will cause strange behavior. I'll show you an example.
Using this a toString() method in your entry.
#Override
public String toString() {
return String.format("{ %d, %d }", key, value);
}
Using this code to print the priority queue.
PriorityQueue<Entry> queue = new PriorityQueue<>();
for (Entry data : entries) {
Entry entry = new Entry(data.getKey(), data.getValue());
if (queue.contains(entry)) {
for (Entry current : queue) { // just for the example
if (current.equals(entry)) {
current.addToValue(entry.getValue());
}
}
} else {
queue.add(entry);
}
}
while (!queue.isEmpty()) // print ordered
System.out.println(queue.poll());
With this data.
List<Entry> entries = Arrays.asList(
new Entry(1, 4),
new Entry(2, 3),
new Entry(2, 5)
);
The output is not correctly sorted { 1, 4 }, { 2, 8 } instead { 2, 8 }, { 1, 4 }, this because the entry with id 2 was mutated after it was added to the collection.
By the other hand, with this data
List<Entry> entries = Arrays.asList(
new Entry(1, 4),
new Entry(2, 8)
);
It prints the output correctly sorted { 2, 8 }, { 1, 4 }.
The solution could be using a HashMap and then a TreeSet.
Map<Integer, Integer> map = new HashMap<>();
for (Entry data : entries) { // here instead read from csv and create entry
map.computeIfPresent(data.getKey(), (k, v) -> v + data.getValue()); // sum priorities
map.putIfAbsent(data.getKey(), data.getValue()); // add when not exists
}
Now that you have a map identified by the key and containing the sumarized values of the priorities, you can use a TreeSet or a PriorityQueue to sort the entries.
You don't need to use your custom Entry, you can use the java Entry and pass a Comparator to the TreeSet / PriorityQueue.
TreeSet<Map.Entry<Integer, Integer>> treeSet = new TreeSet<>((e1, e2) ->
e2.getValue() - e1.getValue() != 0 ?
e2.getValue() - e1.getValue() :
e2.getKey() - e1.getKey());
treeSet.addAll(map.entrySet());
System.out.println(treeSet);
This comparator compares first the priority, if is different, return -1 or 1, following a descendant order.
If the priority is the same, it compares the key, and returns -1 or 1 (0 is not possible because there are not duplicate keys) and order of the keys is descending.

Finding Key in std::unordered_map with custom key

I'm currently creating a custom std::unordered_map declaration with my custom key:
class BASE_DLLSPEC ClientKey
{
private:
// this is always true initially until we call SetClientId
bool emptyId;
// both of these are guaranteed to be unique
QString m_connectId; // ip:port format
QString m_clientId; // {Uuid} format
// ----------
public:
ClientKey(const QString& connectId = "", const QString& clientId = "") :
emptyId(true), m_connectId(connectId), m_clientId(clientId)
{ }
void SetClientId(const QString& clientId)
{
m_clientId = clientId;
emptyId = false;
}
const QString& GetConnectId() const { return m_connectId; }
const QString& GetClientId() const { return m_clientId; }
bool operator==(const ClientKey& other) const
{
int comp1 = QString::compare(m_connectId, other.GetConnectId());
int comp2 = QString::compare(m_clientId, other.GetClientId());
return (comp1 == 0) ||
(!emptyId && comp2 == 0);
}
};
struct BASE_DLLSPEC ClientKeyHash
{
std::size_t operator()(const ClientKey& key) const
{
std::string connectId = key.GetConnectId().toStdString();
std::string clientId = key.GetClientId().toStdString();
std::size_t h1 = std::hash<std::string>()(connectId);
std::size_t h2 = std::hash<std::string>()(clientId);
return h1 ^ (h2 << 1);
}
};
struct BASE_DLLSPEC ClientKeyEqual
{
bool operator()(const ClientKey& lhs, const ClientKey& rhs) const
{
return lhs == rhs;
}
};
typedef std::unordered_map<ClientKey,
ClientPtr,
ClientKeyHash,
ClientKeyEqual> ClientMap;
I'm having difficulties finding a particular key during my iteration. For some reason my client object is never located when I pass in a key for lookup.
ClientKey key = Manager::ClientKey(connectId);
ClientManager& clientManager = Manager::ClientManager::GetInstance();
ClientMap::const_iterator clientIter = clientManager.GetClients().find(key);
Even if the key has already been inserted, clientIter is always pointing to the end iterator position. Do you think this is related to having to re-create these ClientKey values on the stack and then passing them into the map for look-up, or do I have a problem elsewhere? Thank you for the clarification and insight.
At first, some considerations to the emptyId field (do not consider invalid formats - which, by the way, is not checked by you either):
ClientKey k0("hello", "world");
ClientKey k1("hello");
k1.SetClientId("world");
Is there any particular reason that the emtpyId flag should be different for k0 and k1? I personally would say:
The flag is implemented incorrectly.
It is redundant, you get the same information via m_clientId.empty().
Now the reason for failure:
Consider again k0 and k1, but without SetClientId having been called on k1:
ClientKey k0("hello", "world");
ClientKey k1("hello");
Imagine k0 has been inserted in the map, and with k1 you try to find it. What will happen? k1 produces another hash key than k0, and the map will look at a different bucket than where k0 resides at - and will not find anything.
What I think you want to achieve is having several clients for the same connection id and being able to iterate over these for a given connection id. So you might prefer std::unordered_multimap<std::string, ClientPtr> (where the string parameter represents the connection id). You will get all clients for a given connection id via equal_range then, and your class ClientKey gets obsolete.
Your code allows that the following will return true:
ClientKey k1("hello", "world");
ClientKey k2("hello", "");
return k1 == k2;
However, your hash is based on the combination of connectId and clientId.
unordered_map::find does not do an exhaustive search of the map, instead it looks in the bucket for the given hash and compares just the entries in the bucket.
You are generating your test key with just connectId, so it is looking in the bucket for ClientKey(connectId, "") rather than the bucket for ClientKey(connectId, someOtherValue).
You should consider making the hash based exclusively on connectId.
Lastly, note your constructor:
ClientKey(const QString& connectId = "", const QString& clientId = "") :
emptyId(true), m_connectId(connectId), m_clientId(clientId)
{ }
If I write:
ClientKey ck("hello");
should emptyId really be true?

Yaml-cpp (new API): Problems mixing maps and scalars in a sequence

I have a very simple problem parsing a yaml file of this form:
- Foo
- Bar:
b1: 5
I would like to parse the top level keys as strings namely "Foo" and "Bar".
As you can see the first entry in the sequence is a scalar and the second is a map containing one key/value pair. Let's say I've loaded this YAML text into a node called config. I iterate over config in the following way:
YAML::Node::const_iterator n_it = config.begin();
for (; n_it != config.end(); n_it++) {
std::string name;
if (n_it->Type() == YAML::NodeType::Scalar)
name = n_it->as<std::string>();
else if (n_it->Type() == YAML::NodeType::Map) {
name = n_it->first.as<std::string>();
}
}
The problem is parsing the second "Bar" entry. I get the following yaml-cpp exception telling me I'm trying to access the key from a sequence iterator n_it.
YAML::InvalidNode: yaml-cpp: error at line 0, column 0: invalid node; this may result from using a map iterator as a sequence iterator, or vice-versa
If I change the access to this:
name = n_it->as<std::string>();
I get a different yaml-cpp exception which I guess is due to the fact that I'm trying to access the whole map as a std::string
YAML::TypedBadConversion<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >: yaml-cpp: error at line 0, column 0: bad conversion
Can somebody please explain to me what's going wrong?
Edit: new problems
I'm still having problems with this api's handling of maps vs sequences. Now say I have the following structure:
foo_map["f1"] = "one";
foo_map["f2"] = "two";
bar_map["b1"] = "one";
bar_map["b2"] = "two";
I want this to be converted to the following YAML file:
Node:
- Foo:
f1 : one
f2 : two
- Bar:
b1 : one
b2 : two
I would do so by doing:
node.push_back("Foo");
node["Foo"]["b1"] = "one";
...
node.push_back("Bar");
However at the last line node has now been converted from a sequence to a map and I get an exception. The only way I can do this is by outputting a map of maps:
Node:
Foo:
f1 : one
f2 : two
Bar:
b1 : one
b2 : two
The problem with this is if I cannot read back such files. If I iterate over Node, I'm unable to even get the type of the node iterator without getting an exception.
YAML::Node::const_iterator n_it = node.begin();
for (; n_it != config.end(); n_it++) {
if (n_it->Type() == YAML::NodeType::Scalar) {
// throws exception
}
}
This should be very simple to handle but has been driving me crazy!
In your expression
name = n_it->first.as<std::string>();
n_it is a sequence iterator (since it's an iterator for your top-level node), which you've just established points to a map. That is,
YAML::Node n = *n_it;
is a map node. This map node (in your example) looks like:
Bar:
b1: 5
In other words, it has a single key/value pair, with the key a string, and the value a map node. It sounds like you want the string key. So:
assert(n.size() == 1); // Verify that there is, in fact, only one key/value pair
YAML::Node::const_iterator sub_it = n.begin(); // This iterator points to
// the single key/value pair
name = sub_it->first.as<std::string>();
Sample.yaml
config:
key1: "SCALER_VAL" # SCALER ITEM
key2: ["val1", "val2"] #SEQUENCE ITEM
key3: # MAP ITEM
nested_key1: "nested_val"
#SAMPLE CODE for Iterate Yaml Node;
YAML::Node internalconfig_yaml = YAML::LoadFile(configFileName);
const YAML::Node &node = internalconfig_yaml["config"];
for(const auto& it : node )
{
std::cout << "\nnested Key: " << it.first.as<std::string>() << "\n";
if (it.second.Type() == YAML::NodeType::Scalar)
{
std::cout << "\nnested value: " << std::to_string(it.second.as<int>()) << "\n";
}
if (it.second.Type() == YAML::NodeType::Sequence)
{
std::vector<std::string> temp_vect;
const YAML::Node &nestd_node2 = it.second;
for(const auto& it2 : nestd_node2)
{
if (*it2)
{
std::cout << "\nnested sequence value: " << it2.as<std::string>() << "\n";
temp_vect.push_back(it2.as<std::string>());
}
}
std::ostringstream oss;
std::copy(temp_vect.begin(), temp_vect.end(),
std::ostream_iterator<std::string>(oss, ","));
std::cout << "\nnested sequence as string: " <<oss.str() << "\n";
}
if (it2.second.Type() == YAML::NodeType::Map)
{
// Iterate Recursively again !!
}
}
Refer here for more details;
This can also be done with the new C++ loop:
std::string name;
for (const auto &entry: node_x) {
assert(name.empty());
name = entry.first.as<std::string>();
}
The assertion will trigger if the node_x is something else than you think. It should be only one entry in this map.
Try something like this:
- Foo: {}
- Bar:
b1: 15

Creating JSON arrays in Boost using Property Trees

I'm trying to create a JSON array using boost property trees.
The documentation says: "JSON arrays are mapped to nodes. Each element is a child node with an empty name."
So I'd like to create a property tree with empty names, then call write_json(...) to get the array out. However, the documentation doesn't tell me how to create unnamed child nodes. I tried ptree.add_child("", value), but this yields:
Assertion `!p.empty() && "Empty path not allowed for put_child."' failed
The documentation doesn't seem to address this point, at least not in any way I can figure out. Can anyone help?
Simple Array:
#include <boost/property_tree/ptree.hpp>
using boost::property_tree::ptree;
ptree pt;
ptree children;
ptree child1, child2, child3;
child1.put("", 1);
child2.put("", 2);
child3.put("", 3);
children.push_back(std::make_pair("", child1));
children.push_back(std::make_pair("", child2));
children.push_back(std::make_pair("", child3));
pt.add_child("MyArray", children);
write_json("test1.json", pt);
results in:
{
"MyArray":
[
"1",
"2",
"3"
]
}
Array over Objects:
ptree pt;
ptree children;
ptree child1, child2, child3;
child1.put("childkeyA", 1);
child1.put("childkeyB", 2);
child2.put("childkeyA", 3);
child2.put("childkeyB", 4);
child3.put("childkeyA", 5);
child3.put("childkeyB", 6);
children.push_back(std::make_pair("", child1));
children.push_back(std::make_pair("", child2));
children.push_back(std::make_pair("", child3));
pt.put("testkey", "testvalue");
pt.add_child("MyArray", children);
write_json("test2.json", pt);
results in:
{
"testkey": "testvalue",
"MyArray":
[
{
"childkeyA": "1",
"childkeyB": "2"
},
{
"childkeyA": "3",
"childkeyB": "4"
},
{
"childkeyA": "5",
"childkeyB": "6"
}
]
}
What you need to do is this piece of fun. This is from memory, but something like this works for me.
boost::property_tree::ptree root;
boost::property_tree::ptree child1;
boost::property_tree::ptree child2;
// .. fill in children here with what you want
// ...
ptree.push_back( std::make_pair("", child1 ) );
ptree.push_back( std::make_pair("", child2 ) );
But watch out there's several bugs in the json parsing and writing. Several of which I've submitted bug reports for - with no response :(
EDIT: to address concern about it serializing incorrectly as {"":"","":""}
This only happens when the array is the root element. The boost ptree writer treats all root elements as objects - never arrays or values. This is caused by the following line in boost/propert_tree/detail/json_parser_writer.hpp
else if (indent > 0 && pt.count(Str()) == pt.size())
Getting rid of the "indent > 0 &&" will allow it to write arrays correctly.
If you don't like how much space is produced you can use the patch I've provided here
When starting to use Property Tree to represent a JSON structure I encountered similar problems which I did not resolve. Also note that from the documentation, the property tree does not fully support type information:
JSON values are mapped to nodes containing the value. However, all type information is lost; numbers, as well as the literals "null", "true" and "false" are simply mapped to their string form.
After learning this, I switched to the more complete JSON implementation JSON Spirit. This library uses Boost Spirit for the JSON grammar implementation and fully supports JSON including arrays.
I suggest you use an alternative C++ JSON implementation.
In my case I wanted to add an array to a more or less arbitrary location, so, like Michael's answer, create a child tree and populate it with array elements:
using boost::property_tree::ptree;
ptree targetTree;
ptree arrayChild;
ptree arrayElement;
//add array elements as desired, loop, whatever, for example
for(int i = 0; i < 3; i++)
{
arrayElement.put_value(i);
arrayChild.push_back(std::make_pair("",arrayElement))
}
When the child has been populated, use the put_child() or add_child() function to add the entire child tree to the target tree, like this...
targetTree.put_child(ptree::path_type("target.path.to.array"),arrayChild)
the put_child function takes a path and a tree for an argument and will "graft" arrayChild into targetTree
As of boost 1.60.0, problem persists.
Offering a Python 3 workaround (Gist), which can be syscalled just after boost::property_tree::write_json.
#!/usr/bin/env python3
def lex_leaf(lf: str):
if lf.isdecimal():
return int(lf)
elif lf in ['True', 'true']:
return True
elif lf in ['False', 'false']:
return False
else:
try:
return float(lf)
except ValueError:
return lf
def lex_tree(j):
tj = type(j)
if tj == dict:
for k, v in j.items():
j[k] = lex_tree(v)
elif tj == list:
j = [lex_tree(l) for l in j]
elif tj == str:
j = lex_leaf(j)
else:
j = lex_leaf(j)
return j
def lex_file(fn: str):
import json
with open(fn, "r") as fp:
ji = json.load(fp)
jo = lex_tree(ji)
with open(fn, 'w') as fp:
json.dump(jo, fp)
if __name__ == '__main__':
import sys
lex_file(sys.argv[1])
If you want JSON in C++, there's no need for Boost. With this library you can get JSON as a first class data type that behaves like an STL container.
// Create JSON on the fly.
json j2 = {
{"pi", 3.141},
{"happy", true},
{"name", "Niels"},
{"nothing", nullptr},
{"answer", {
{"everything", 42}
}},
{"list", {1, 0, 2}},
{"object", {
{"currency", "USD"},
{"value", 42.99}
}}
};
// Or treat is as an STL container; create an array using push_back
json j;
j.push_back("foo");
j.push_back(1);
j.push_back(true);
// also use emplace_back
j.emplace_back(1.78);
// iterate the array
for (json::iterator it = j.begin(); it != j.end(); ++it) {
std::cout << *it << '\n';
}
Confused with the official document and the above answers.
Below is what I understand.
Property Tree consists of nodes.
Each node is like below
struct ptree
{
map<key_name,value> data;
vector<pair<key_name,ptree>> children;
};
To put 'value' into data with 'put'
To put 'node' into children with 'push_back'\
// Write
bt::ptree root;
bt::ptree active;
bt::ptree requested;
bt::ptree n1, n2, n3;
n1.put("name", "Mark");
n1.put("age", 20);
n1.put("job", "aaa");
n2.put("name", "Rosie");
n2.put("age", "19");
n2.put("job", "bbb");
n3.put("name", "sunwoo");
n3.put("age", "10");
n3.put("job", "ccc");
active.push_back ({ "",l1 });
active.push_back ({ "",l2 });
requested.push_back({ "",l3 });
root.push_back ({"active", active});
root.push_back ({"requested", requested});
bt::write_json("E:\\1.json", root);
// READ
bt::ptree root2;
bt::ptree active2;
bt::ptree requested2;
bt::ptree r1, r2, r3;
bt::read_json("E:\\1.json", root2);
// loop children
for (auto& [k,n] : root.get_child("active"))
{
cout << n.get<string>("name", "unknown");
cout << n.get<int> ("age" , 11);
cout << n.get<string>("job" , "man");
cout << endl << flush;
}