How do I change the map container's internal sorting scheme? - c++

I'm a beginner C++ programmer so there are language constructs that I don't understand which prevents me from understanding map's API (for your reference, here)
More to the point, some questions:
How do I change the internal sorting scheme of map so that, given that we're working with map::<string, ...>, the key values are sorted alphabetically?
More specifically about the map::key_comp, is it a define-and-forget thing where once we define what it means for two elements of the same type to be "unequal (one is less than the other)", then the sorting is done internally and automatically - so all we need to is insert key/value pairs? Or do we have to define equality/ordering and then call the function explicitly to return a boolean to implement ordered insertion?

Here's an example of how you give the sorted map a template argument to use a non-default sort:
std::map<int, int, std::greater<int> > m;
Taken from C++ std::map items in descending order of keys
Also, for a more complex example: how to declare custom sort function on std::map declaration?

[Solved] Solution I'd chosen
Suppose your map was: map<int,vector<int>,compAB> myMap;
Then you need to do 2 things to define "compAB":
Define a comparison function that returns true or false/(or 1 or 0). I haven't tested whether this function can take multiple arguments (although I don't see why it can't so long as it returns a bool. This function should specify how you intend to order two objects of the same type as the key value in your your map.
bool compare(int a, int b)
{
if(a < b) return true;
return false; //Default
}
The API for the map::key_comp object (here indicates that the comparison function needs to return true if the first argument is "less than"/"comes before" the second argument. It should return false otherwise source). Here, I've used a simple criteria for determining "less than": a precedes b if a<b, as the computer evaluates it.
Create a struct whose member consist of just an operator-overload:
struct compAB
{
bool operator()(int i1, int i2){
return compare(i1, i2);
}
};
Then, you can make the declaration: map<int,vector<int>,compAB> myMap; and any calls to insertion() will insert the pairs you've indicated according to the key-ordering scheme you specified
For instance:
#include <iostream>
#include <map>
bool compare_descend(int a, int b)
{
//specifies that when a>b return true -> a FOLLOWS b
if(b < a) return true;
return false;
}
bool compare_ascend(int a, int b)
{
//specifies that when a<b return true -> a PRECEDES b
if(a < b) return true;
return false; //Default
}
struct comp_asc
{
bool operator()(int i1, int i2)
{
return compare_ascend(i1, i2);
}
};
int main()
{
std::cout << "int_map_asc: [key,value]\n";
//Map declaration via class function
std::map<int,std::string,comp_asc> int_map_asc;
//Insert pairs into the map
int_map_asc.insert( std::pair<int,std::string>(0, "Alan") );
int_map_asc.insert( std::pair<int,std::string>(1, "Baye") );
int_map_asc.insert( std::pair<int,std::string>(2, "Carl") );
int_map_asc.insert( std::pair<int,std::string>(3, "David") );
//Iterate & print
std::map<int,std::string,comp_asc>::iterator a_it;
for(a_it=int_map_asc.begin(); a_it!=int_map_asc.end(); a_it++)
std::cout << "[" << a_it->first << "," << a_it->second << "]\n";
std::cout << "\nint_map_desc: [key,value]\n";
//Map declaration via function pointer as compare
bool(*fn_compare_desc)(int,int) = compare_descend; //Create function ptr to compare_descend()
std::map<int,std::string,bool(*)(int,int)> int_map_desc(fn_compare_desc); //fn ptr passed to constructor
//Insert pairs into the map
int_map_desc.insert( std::pair<int,std::string>(0, "Alan") );
int_map_desc.insert( std::pair<int,std::string>(1, "Baye") );
int_map_desc.insert( std::pair<int,std::string>(2, "Carl") );
int_map_desc.insert( std::pair<int,std::string>(3, "David") );
//Ititerate & print
std::map<int,std::string,bool(*)(int,int)>::iterator d_it;
for(d_it=int_map_desc.begin(); d_it!=int_map_desc.end(); d_it++)
std::cout << "[" << d_it->first << "," << d_it->second << "]\n";
return 0;
}
Output:
int_map_asc: [key,value]
[0,Alan]
[1,Baye]
[2,Carl]
[3,David]
int_map_desc: [key,value]
[3,David]
[2,Carl]
[1,Baye]
[0,Alan]
Additional examples here.

Related

remove a pair from a list of pairs c++

I'm doing this all in classes, I've a simple question, I've a private list of pairs:
std::list<std::pair<std::string, size_t>> pokemons_;
to which I've passed certain values as:
{("Pikachu", 25),
("Raticate", 20),
("Raticate", 20),
("Bulbasaur", 1),
("Pikachu", 25),
("Diglett", 50)};
Now I want to remove a pair by calling a public remove function of my class.
bool PokemonCollection::Remove(const std::string& name, size_t id){};
what I don't understand is how to compare the string and id value while calling the remove function:
collection.remove("Raticate", 20);
"collection Is an object of my class"
what I've implemented till now by the help of other forums and reading internet and cpp reference is:
bool PokemonCollection::Remove(const std::string& name, size_t id){
bool found;
string x;
size_t y;
for (auto currentPair : pokemons_){
pair<string, size_t> currentpair = currentPair;
x=currentpair.first;
y=currentpair.second;
pokemons_.erase(pokemons_.begin()+i)
for (int i=0; i<pokemons_.size(); i++){
if (pokemons_[i].first == x && pokemons_[i].second == y){
// pokemons_.erase(pokemons_.begin() +i);
cout<<"FOUND!!!!!!!!!!!!!!!";
found = true;
return true;
}else{
found = false;
}
}
}
return found;
}
but this remove function of mine gives some errors I don't really understand.
also it gives so much errors on the commented line where I used erase function. I just need some help to compare the string and id and remove that pair from original private list of my class.
MY FUNCTION
```bool PokemonCollection::Remove(const std::string& name, size_t id){
bool found;
//string x;
//size_t y;
pokemons_.remove_if([&](std::pair<std::string, size_t>& p){return found = true and p.first==name and p.second==id;});
if(found==true){
return true;
}else{
found = false;
}
return found;
}```
There is a very simple solution to your problem.
The std::listhas a function remove_if, which will do everything for you. See here.
Please see the below code as an example:
#include <iostream>
#include <string>
#include <utility>
#include <list>
std::list<std::pair<std::string, size_t>> poke
{{"Pikachu", 25},
{"Raticate", 20},
{"Raticate", 20},
{"Bulbasaur", 1},
{"Pikachu", 25},
{"Diglett", 50}};
void remove(const std::string& s, size_t i) {
poke.remove_if([&](std::pair<std::string, size_t>& p){return p.first== s and p.second==i;});
}
int main() {
remove("Raticate", 20);
for (const auto& [s,i] : poke)
std::cout << s << '\t' << i <<'\n';
}
Some more information:
As you can read in the remove_if documentation of the std::list, it needed to be called as following:
void remove_if( UnaryPredicate p );
The problem for you maybe the "UnaryPredicate". We can read:
unary predicate which returns ​true if the element should be removed.
and, we can read further:
The expression p(v) must be convertible to bool for every argument v of type (possibly const) T, regardless of value category, and must not modify v. Thus, a parameter type of T&is not allowed, nor is T unless for T a move is equivalent to a copy (since C++11). ​
But this will also not help you very much. Basically a predicate is, extremely simplified, a function (object).
So, remove_if will iterate over all elements in the std::list and call this "function". If this "function" returns true, then then the associated list-element will be removed.
The C++ standard defines Predicate as follows (25/7):
The Predicate parameter is used whenever an algorithm expects a function object that when applied to the result of dereferencing the corresponding iterator returns a value testable as true. In other words, if an algorithm takes Predicate pred as its argument and first as its iterator argument, it should work correctly in the construct if (pred(*first)){...}. The function object pred shall not apply any non-constant function through the dereferenced iterator. This function object may be a pointer to function, or an object of a type with an appropriate function call operator.
For the above case, I used a lambda expression as function object. This mechanism is widely used in C++. Please read about that.
Unfortunately remove_if will not return any value. There are really several methods to build a solution here.
Let me show you onesolution, by still using the lambda.
#include <iostream>
#include <string>
#include <utility>
#include <list>
std::list<std::pair<std::string, size_t>> poke
{ {"Pikachu", 25},
{"Raticate", 20},
{"Raticate", 20},
{"Bulbasaur", 1},
{"Pikachu", 25},
{"Diglett", 50} };
bool remove(const std::string& s, size_t i) {
bool found = false;
poke.remove_if([&](std::pair<std::string, size_t>& p) {bool rv = (p.first == s and p.second == i); if (rv) found = true; return rv; });
return found;
}
int main() {
if (remove("Raticate", 20))
std::cout << "\nFound\n\n";
else
std::cout << "\nNot Found\n\n";
for (const auto& [s, i] : poke)
std::cout << s << '\t' << i << '\n';
}
It can be done even simpler than Armin Montigny's solution. std::pair<> comes with comparison operators, so you don't need a custom function to check if an element of the list is equal to a given pair:
void remove(const std::string& s, size_t i) {
poke.remove({s, i});
}
Since C++20, std::list's remove() and remove_if() return a count of the number of elements removed. If you need to be compatible with earlier versions, you could always check the result of poke.size() before and after the call to remove() to see if the size of the list has changed.

Does C++ guarantee the order of operands in comparisons made by standard containers?

TL;DR: I have a use case where it matters whether WidgetEqualTo()(new_widget, widget_inside_container) or WidgetEqualTo()(widget_inside_container, new_widget) is called.
Identical Widgets may be re-created many times so I have a WidgetPool (for the purposes of this example, a global wrapper around std::vector<const Widget*>) and a smart constructor:
const Widget* combine(const Widget* a, const Widget* b) {
static std::unordered_map<std::pair<int, int>, int> cache;
std::pair<int, int> ab = std::make_pair(a->id(), b->id());
const auto it = cache.find(ab);
if (it == cache.end()) {
// The Widget ctor sets this->id() to WidgetPool::size()
// and appends this to WidgetPool.
const Widget* result = new Widget(a, b);
cache[ab] = result->id();
return result;
} else {
return WidgetPool::get_widget(it->second);
}
}
I also have a container where Widgets get inserted in the order of their creation. Say, std::unordered_set<const Widget*, WidgetHash, WidgetEqualTo>, where WidgetEqualTo looks like this:
struct WidgetEqualTo {
bool operator()(const Widget* a, const Widget* b) const {
if (a == b) {
return true;
}
// My Widgets obey the associative law:
// tedious_comparison(new Widget(new Widget(p, q), r),
// new Widget(p, new Widget(q, r))) == true.
const bool are_equal = tedious_comparison(a, b);
if (are_equal) {
// Cache the result of the comparison.
// Retain the older Widget.
if (a->id() < b->id()) { // (***)
WidgetPool::set_widget(b->id(), a);
delete b;
} else {
WidgetPool::set_widget(a->id(), b);
delete a;
}
}
return are_equal;
}
};
If WidgetEqualTo() were always called with (new_element, element_already_inside_unordered_set) or the other way around, I could remove one branch of the test marked with (***). FWIW, libstdc++ appears to call WidgetEqualTo()(new_element, old_element). Does the C++ standard guarantee this behavior?
No.
[C++11: 25.2.5/3]: Each unordered associative container is parameterized by Key, by a function object type Hash that meets the Hash requirements (17.6.3.4) and acts as a hash function for argument values of type Key, and by a binary predicate Pred that induces an equivalence relation on values of type Key. Additionally, unordered_map and unordered_multimap associate an arbitrary mapped type T with the Key.
Table 17 tells us the EqualityComparable requirements:
== is an equivalence relation, that is, it has the following properties:
For all a, a == a.
If a == b, then b == a.
If a == b and b == c, then a == c.
(gah! comma splice!)
And note that the given semantics of the comparator makes no mention of which way around the operands are given:
[C++11: 25.2.5/5]: Two values k1 and k2 of type Key are considered equivalent if the container’s key_equal function object returns true when passed those values. [..]
Put simply, your program has undefined behaviour if it matters which order the arguments are supplied.
This is not a C++ oddity, either; equivalence implies symmetry throughout mathematics.

Why does std::set seem to force the use of a const_iterator?

Consider the simple program below, which attempts to iterate through the values of a set using NON-const references to the elements in it:
#include <set>
#include <iostream>
class Int
{
public:
Int(int value) : value_(value) {}
int value() const { return value_; }
bool operator<(const Int& other) const { return value_ < other.value(); }
private:
int value_;
};
int
main(int argc, char** argv) {
std::set<Int> ints;
ints.insert(10);
for (Int& i : ints) {
std::cout << i.value() << std::endl;
}
return 0;
}
When compiling this, I get an error from gcc:
test.c: In function ‘int main(int, char**)’:
test.c:18:18: error: invalid initialization of reference of type ‘Int&’ from expression of type ‘const Int’
for (Int& i : ints) {
^
Yes, I know I'm not actually trying to modify the elements in the for loop. But the point is that I should be able to get a non-const reference to use inside the loop, since the set itself is not const qualified. I get the same error if I create a setter function and use that in the loop.
A set is like a map with no values, only keys. Since those keys are used for a tree that accelerates operations on the set, they cannot change. Thus all elements must be const to keep the constraints of the underlying tree from being broken.
std::set uses the contained values to form a fast data structure (usually, a red-black tree). Changing a value means the whole structure needs to be altered. So, forcing constness, std::set prevents you from pushing it into a non-usable state.
From the cpp reference:
In a set, the value of an element also identifies it (the value is
itself the key, of type T), and each value must be unique. The value
of the elements in a set cannot be modified once in the container (the
elements are always const), but they can be inserted or removed from
the container.
The behaviour is by design.
Giving you a non-const iterator could inspire you to change the element in the set; the subsequent iterating behaviour would then be undefined.
Note that the C++ standard says that set<T>::iterator is const so the old-fashioned pre C++11 way still wouldn't work.
Adding on nate's answer:
A set is like a map with no values, only keys. Since those keys are used for a tree that accelerates operations on the set, they cannot change. Thus all elements must be const to keep the constraints of the underlying tree from being broken.
With C++17 there is the new extract member function, so an alternative to const_cast could be:
#include <iostream>
#include <string_view>
#include <set>
struct S
{
int used_for_sorting;
bool not_used_for_sorting;
bool operator<(const S &rhs) const
{ return used_for_sorting < rhs.used_for_sorting; }
};
void print(std::string_view comment, const std::set<S> &data)
{
std::cout << comment;
for (auto datum : data)
std::cout << " {" << datum.used_for_sorting
<< ',' << datum.not_used_for_sorting
<< '}';
std::cout << '\n';
}
int main()
{
std::set<S> cont = {{1, false}, {2, true}, {3, false}};
print("Start:", cont);
// Extract node handle and change key
auto nh = cont.extract({1, false});
nh.value().not_used_for_sorting = true;
print("After extract and before insert:", cont);
// Insert node handle back
cont.insert(std::move(nh));
print("End:", cont);
}
Probably useful as hot-fix. In general it's hard to see any advantage over a std::map.

Find the element in a std::set in C++11

What would be the key in case of Objects being inserted into std::set? As for the following example, I am inserting objects of class Classcomp into the std::set. However, I want to find whether an object of Classcomp with a particular 'id = 1' exists in the std::set or not?
#include <iostream>
#include <set>
struct Classcomp {
int val = 0;
int id = 0; ///ID for the object
bool operator() (const Classcomp& lhs, const Classcomp& rhs) const
{return lhs.val<rhs.val;}
};
int main ()
{
Classcomp c1,c2,c3,c4;
c1.val = 92;c1.id = 2;
c2.val = 94;c2.id = 3;
c3.val = 10;c3.id = 1;
std::set<Classcomp,Classcomp> fifth; // class as Compare
fifth.insert(c1);fifth.insert(c2);fifth.insert(c3);
for (auto x: fifth) {std::cout << x.id << " " << x.val << std::endl;}
if (fifth.find()) {std::cout << "Got it";} //What should I pass as arguments to fifth.find()?
return 0;
}
Sets is different from the map by exactly the fact that values of objects inside the set are their keys. If you need to separate a key from the value, you need a key-value container, such as std::map or std::unordered_map.
Clarification
I am not talking about the option of simply iterating over all objects in the set and checking every object to have specified key - which is easily done with std::find. If that's what you want, please clarify.
As your class is sorted by field val you have following options:
iterate over all elements and find one with particular id by std::find or std::find_if
create another container and sort there by id (maybe using std::share_ptr to avoid duplicates)
use boost::multiindex and create index by val as well as id

How to Represent a Set of Pointers with Customized Compare but Maintaining the Original Raw Pointer Duplicate Comparison

Basically, I want to save a set of pointers, which should be sorted by my customized compare function, but the uniqueness should still be determined by the pointer itself.
However:
#include <iostream>
#include <string>
#include <set>
#include <utility>
#include <functional>
using namespace std;
// count, word
typedef pair<int, string> WordFreq;
struct WordFreqPointerCmp
{
bool operator()(const WordFreq* lhs, const WordFreq* rhs) const
{
return lhs->first > rhs->first;
}
};
int main()
{
set<WordFreq*, WordFreqPointerCmp> s;
s.insert(new WordFreq(1, "word1")); // Inserted
s.insert(new WordFreq(1, "word2")); // This is not inserted
s.insert(new WordFreq(3, "word3")); // Inserted
cout << s.size() << endl;
for (set<WordFreq*, WordFreqPointerCmp>::iterator it = s.begin();
it != s.end(); ++it)
{
cout << (*it)->second << ": " << (*it)->first << endl;
}
return 0;
}
/* Output:
2
word3: 3
word1: 1
*/
As you can see that the ordering is correct, but the duplicate testing is wrong. What I am trying to do is:
For ordering, I want to use WordFreqPointerCmp;
For duplicate testing, I want to use the original meaning of raw Pointer comparsion, i.e., the address comparison, which means, even the following set should have two entries in the set;
set<WordFreq*, WordFreqPointerCmp> s;
s.insert(new WordFreq(1, "word1"));
s.insert(new WordFreq(1, "word1"));
I also tried the following, but same result:
template<>
struct greater<WordFreq*>
{
bool operator()(WordFreq* const& lhs, WordFreq* const& rhs) const
{
return lhs->first > rhs->first;
}
};
set<WordFreq*, greater<WordFreq*> > s;
while this post is ancient, I've just faced the same issue, so it may help somebody..
In your code you only handle one value, but what if values are the same? Then set treats it as the same element. The proper solution would be to extend your compare function to give set additional information how to test for duplicates. It can be something arbitrary like comparing strings, for example in your case:
struct WordFreqPointerCmp
{
bool operator()(const WordFreq* lhs, const WordFreq* rhs) const
{
if (lhs->first == rhs->first)
return lhs->second > rhs->second;
else
return lhs->first > rhs->first;
}
};
I am not sure what the problem is. Since you want the first component of your pair to be the key that determines uniqueness, inserting two "WordFreq" with key = 1 should lead to the second evict the first. Results match expectation here.
Update: I guess, I misunderstood something. Since you want duplicate keys, you are probably looking for multimap.
Update 2: To make this work you need to add a step before adding a new object: Iterate over all values of the same key, and kick those out that point to the object being added. Also, I forgot to mention there is multiset which is probably what you'd prefer.
I admit, here is where Java's HashSet with it's separate order and equality tests come in handy. Maybe you can find a C++ version of it.