I was trying to make a map sort by value using a custom comparator but I couldn't figure out why I kept getting the error of "no matching call to compareByVal"
Here's what I had in my main.cpp:
#include <map>
#include <iostream>
struct compareByVal {
bool operator[](const std::pair<int,int> & a, const std::pair<int,int> & b)
return a.second < b.second;
}
int main() {
std::map<int,int,compareByVal> hash;
hash[1] = 5;
hash[2] = 2;
hash[3] = 10;
std::cout << hash.begin()->first << std::endl;
}
The first, simple problem is
struct compareByVal {
bool operator[](const std::pair<int,int> & a, const std::pair<int,int> & b)
return a.second < b.second;
}
should be
struct compareByVal {
bool operator()(const std::pair<int,int> & a, const std::pair<int,int> & b) const {
return a.second < b.second;
}
};
The second, serious problem is the signature of the compare is wrong. It should be
struct compareByVal {
bool operator()(const int leftKey, const int rightKey) const;
}
You can't access the value in the compare function. There is no (simple) way to sort a map by value.
Simply put, you cannot. Not sure which compiler you're using, but clang and gcc both give useful messages. with context.
clang:
static_assert(__is_invocable<_Compare&, const _Key&, const _Key&>{},
gcc:
if (__i == end() || key_comp()(__k, (*__i).first))
You can see that clang and gcc are both calling the compare method with only they key, and not a value. This is simply how maps work.
If you want to sort by value, you would have to create your own custom map, or, more realistically, use the value as the key instead. Creating your own map to achieve this would be more difficult than you'd think, since it would have to sort after any value is modified.
If you want to sort a std::map by its value, then you are using the wrong container. std::map is sorted by the keys by definition.
You can wrap key and value:
struct foo {
int key;
int value;
};
and then use a std::set<foo> that uses a comparator that only compares foo::value.
Well, first, the reason you're getting the error: "no matching call to compareByVal" is because map's comparator works only with the keys. So the comparator should like:
struct compareByVal {
template <typename T>
bool operator()(const T& a, const T& b) const
return a < b;
}
Coming on to what you want to achieve, I see two ways of doing so:
Copy all the elements of the map to a std::vector and sort that:
std::vector<std::pair<int,int> > v(hash.begin(), hash.end());
std::sort(v.begin(), v.end(), [](const auto& a, const auto& b) { return a.second < b.second; });
Copy all the elements of the map to another map with keys as values and values as keys. If values of your map are not unique, you can use a std::multimap instead.
This may be an X-Y issue.
If you need to sort by both key and value, then a single std::map may not be the most efficient choice.
In database theory, all the data would be placed into a single table. An index table would be created describing the access or sorting method. Data that needs to be sorted in more than one method would have multiple index tables.
In C++, the core table would be a std::vector. The indices would be std::map<key1, vector_index>, std::map<key2, vector_index>, where vector_index is the index of the item in the core table.
Example:
struct Record
{
int age;
std::string name;
};
// Core table
std::vector<Record> database;
// Index by age
std::map<int, unsigned int> age_index_table;
// Index by name
std::map<std::string, unsigned int> name_index_table;
// Fetching by age:
unsigned int database_index = age_index_table[42];
Record r = database[database_index];
// Fetching by name:
unsigned int database_index = name_index_table["Harry Potter"];
Record r = database[database_index];
You can learn more by searching the internet for "database index tables c++".
If it looks like a database and smells like a database ...
Related
I want to have a stl list of objects where each object contains two int's.
Afterwards I want to sort the list with stl::sort after the value of the first int.
How do I tell the sort function that it's supposed to sort after the first int?
You can specify a custom sort predicate. In C++11 this is best done with a lambda:
typedef std::pair<int, int> ipair;
std::list<ipair> thelist;
thelist.sort([](const ipair & a, const ipair & b) { return a.first < b.first; });
In older versions of C++ you have to write an appropriate function:
bool compFirst(const ipair & a, const ipair & b) { return a.first < b.first; }
thelist.sort(compFirst);
(Instead if ipair you can of course have your own data structure; just modify the comparison function accordingly to access the relevant data member.)
Finally, if this makes sense, you can also equip your custom class with an operator<. That allows you to use the class freely in any ordered context, but be sure to understand the consequences of that.
You can do something like this:
typedef std::pair<int,int>;
list<my_type> test_list;
bool my_compare (my_type a, my_type b)
{
return a.first < b.first;
}
test_list.sort(my_compare);
If the type was a struct or class it would work something like this:
struct some_struct{
int first;
int second;
};
list<some_struct> test_list;
bool my_compare (const some_struct& a,const some_struct& b)
{
return a.first < b.first;
}
test_list.sort(my_compare);
Or alternatively you can define operator < for your struct and just call test_list.sort()
std::list<T>::sort has a one-argument form, with the first argument being the comparison function.
I was trying to make a map sort by value using a custom comparator but I couldn't figure out why I kept getting the error of "no matching call to compareByVal"
Here's what I had in my main.cpp:
#include <map>
#include <iostream>
struct compareByVal {
bool operator[](const std::pair<int,int> & a, const std::pair<int,int> & b)
return a.second < b.second;
}
int main() {
std::map<int,int,compareByVal> hash;
hash[1] = 5;
hash[2] = 2;
hash[3] = 10;
std::cout << hash.begin()->first << std::endl;
}
The first, simple problem is
struct compareByVal {
bool operator[](const std::pair<int,int> & a, const std::pair<int,int> & b)
return a.second < b.second;
}
should be
struct compareByVal {
bool operator()(const std::pair<int,int> & a, const std::pair<int,int> & b) const {
return a.second < b.second;
}
};
The second, serious problem is the signature of the compare is wrong. It should be
struct compareByVal {
bool operator()(const int leftKey, const int rightKey) const;
}
You can't access the value in the compare function. There is no (simple) way to sort a map by value.
Simply put, you cannot. Not sure which compiler you're using, but clang and gcc both give useful messages. with context.
clang:
static_assert(__is_invocable<_Compare&, const _Key&, const _Key&>{},
gcc:
if (__i == end() || key_comp()(__k, (*__i).first))
You can see that clang and gcc are both calling the compare method with only they key, and not a value. This is simply how maps work.
If you want to sort by value, you would have to create your own custom map, or, more realistically, use the value as the key instead. Creating your own map to achieve this would be more difficult than you'd think, since it would have to sort after any value is modified.
If you want to sort a std::map by its value, then you are using the wrong container. std::map is sorted by the keys by definition.
You can wrap key and value:
struct foo {
int key;
int value;
};
and then use a std::set<foo> that uses a comparator that only compares foo::value.
Well, first, the reason you're getting the error: "no matching call to compareByVal" is because map's comparator works only with the keys. So the comparator should like:
struct compareByVal {
template <typename T>
bool operator()(const T& a, const T& b) const
return a < b;
}
Coming on to what you want to achieve, I see two ways of doing so:
Copy all the elements of the map to a std::vector and sort that:
std::vector<std::pair<int,int> > v(hash.begin(), hash.end());
std::sort(v.begin(), v.end(), [](const auto& a, const auto& b) { return a.second < b.second; });
Copy all the elements of the map to another map with keys as values and values as keys. If values of your map are not unique, you can use a std::multimap instead.
This may be an X-Y issue.
If you need to sort by both key and value, then a single std::map may not be the most efficient choice.
In database theory, all the data would be placed into a single table. An index table would be created describing the access or sorting method. Data that needs to be sorted in more than one method would have multiple index tables.
In C++, the core table would be a std::vector. The indices would be std::map<key1, vector_index>, std::map<key2, vector_index>, where vector_index is the index of the item in the core table.
Example:
struct Record
{
int age;
std::string name;
};
// Core table
std::vector<Record> database;
// Index by age
std::map<int, unsigned int> age_index_table;
// Index by name
std::map<std::string, unsigned int> name_index_table;
// Fetching by age:
unsigned int database_index = age_index_table[42];
Record r = database[database_index];
// Fetching by name:
unsigned int database_index = name_index_table["Harry Potter"];
Record r = database[database_index];
You can learn more by searching the internet for "database index tables c++".
If it looks like a database and smells like a database ...
I have a list filled with this struct:
struct singlePaymentStruct
{
std::string payer;
int payment;
double amount;
std::time_t timeRec;
singlePaymentStruct() {
payer="Empty";
payment=0;
amount=0;
timeRec = time(0);
}
};
I want to be able to sort this list by any of the fields. How exactly do I do this?
I didn't quite understand how sort method works with something more complex than just a list of records...
Solution found:
singlePaymentList.sort( []( const singlePaymentStruct &a, const singlePaymentStruct &b)
{return a.payer > b.payer;}
);
1.overloading operator<
you can do this by overloading the < operator
struct Foo{
int bar;
bool operator<(Foo &x){
return bar < x.bar;
}
};
2.using lambda expressions
(what is lambda expression?)
Foo array[10];
std::sort(array,array + 10,[](Foo const &l, Foo const &r) {
return l.bar < r.bar; });
3.using custom compare functions
If the possible fields to be used for sorting are known prior, it may be easier to read to implement custom compare functions specifically for the sorting.
struct Foo {
int bar;
SpecialType daa; // Assume daa.IsLessThan() available.
static bool lessBar(const Foo& l, const Foo& r) {
return l.bar < r.bar;
}
static bool lessDaa(const Foo& l, const Foo& r) {
return l.daa.IsLessThan(r.daa);
}
};
Foo array1[10]; // To be sorted by Foo::bar
Foo array2[10]; // To be sorted by Foo::daa
std::sort(array1, array1+10, Foo::lessBar);
std::sort(array2, array2+10, Foo::lessDaa);
std::sort accepts a third optional parameter that is a comparator function. This function should behave as < between elements (i.e. return true when the first is "less than" the second.
For example to sort an std::vector of your structures on increasing payment value what you can do is:
std::sort(data.begin(), data.end(),
[](const singlePaymentStruct& a, const singlePaymentStruct& b) {
return a.payment < b.payment;
});
let the array be struct singlePaymentStruct a[N]
sort(a,a+N,cmp);
bool cmp(struct singlePaymentStruct x, struct singlePaymentStruct y)
{
return x.field < y.field ; //or anything you want to do and return boolean
}
How it works under the hood?
Simply put basically it uses some sorting algoritm like quicksort or mergesort.
Why do we specify comparator functor ?
Well we need that comparator functor to decide the ordering of elements.
The basic thing is in any sorting algortihm the basic operation is comparison..and if we can specify that we are basically controlling the sorting operation.
Hope now you get the pieces together. That's why cmp() takes two values which it will compare and based on which order them.
Word.
I have a struct, containing a single field that I would like set to use for comparison and equivalence, and other fields as metadata:
struct read_tag{
unsigned int read_id; // want std::set to use this
int offset; // metadata
bool orientation; // metadata
};
I have a functor to do the job:
struct read_tag_compare {
bool operator() (const read_tag &a, const read_tag &b) const {
return a.read_id > b.read_id
}
};
and decl. the required set as
std::set<read_tag, read_tag_compare> block;
Everything so far compiles. The problem is below:
How do I make a set containing std::set<read_tag, read_tag_compare>. I want something like this:
std::set< std::set<read_tag, read_tag_compare> > blocks;
blocks.insert(a_block); // comp error
But this gives me a very large, and hard to decipher error.
I thought it would recursively check how the inner sets are compared and extend this to the outer sets. All one had to do is define the comparator for the inner most set.
For example
std::set<std:set<unsigned int>> set_o_sets;
works fine, without me having to define how to compare std::set<unsigned int>
Any help is mucho appreciated :D
The <-comparison on std::set uses std::lexicographical_compare without comparator, i.e. it just forwards to < on the element type. (This is a limitation of the standard library, since this is defined for all containers, not just the ordered-associative ones.) So what you need is a custom comparator for sets of sets that uses the correct overload of lexicographical comparison:
using read_tag_set = std::set<read_tag, read_tag_compare>;
struct read_tag_set_compare {
bool operator()(const read_tag_set &a, const read_tag_set &b) const noexcept {
return std::lexicographical_compare(a.begin(), a.end(),
b.begin(), b.end(), a.key_comp());
// ^^^^^^^^^^^^
}
};
Now use: std::set<read_tag_set, read_tag_set_compare>
The code shows why there isn't an obvious "fix" to the ordered associative containers that would make this "just work": If the containers use custom, stateful predicates, then it's not in general guaranteed that the members of two distinct containers can actually be compared with one another at all. All you know is that the elements within one container are comparable with that container's comparator. So when you're using a custom comparator, you better also say explicitly how two distinct containers relate, and you assert explicitly that it makes sense to compare two containers.
It compiled with no error with my g++-5.3.1 ubuntu..
#include<set>
#include<iostream>
using namespace std;
struct read_tag{
unsigned int read_id; // want std::set to use this
int offset; // metadata
bool orientation; // metadata
};
struct read_tag_compare {
bool operator() (const read_tag &a, const read_tag &b) const {
return a.read_id > b.read_id;
}
};
struct read_compare {
bool operator() (const set<read_tag, read_tag_compare> &a, const set<read_tag, read_tag_compare> &b) const {
return true;
}
};
int main()
{
set<read_tag, read_tag_compare> block;
set<set<read_tag, read_tag_compare>, read_compare> blocks;
blocks.insert(block)
}
Above was what I compiled.
How do I sort an STL vector based on two different comparison criterias? The default sort() function only takes a single sorter object.
You need to combine the two criteria into one.
Heres an example of how you'd sort a struct with a first and second field
based on the first field, then the second field.
#include <algorithm>
struct MyEntry {
int first;
int second;
};
bool compare_entry( const MyEntry & e1, const MyEntry & e2) {
if( e1.first != e2.first)
return (e1.first < e2.first);
return (e1.second < e2.second);
}
int main() {
std::vector<MyEntry> vec = get_some_entries();
std::sort( vec.begin(), vec.end(), compare_entry );
}
NOTE: implementation of compare_entry updated to use code from Nawaz.