-1

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;
}
Hint33
  • 15
  • 2
  • 8
  • 2
    Because it doesn't care about the value. It would only pass in the key. You cannot sort by value, unless you make your own custom map. Or, you know, just make the value the key? In your example, `hash[1] = 5` actually inserts `{1,0}` then changes the `0` to a `5` afterward with the assignment. How would you sort this? – ChrisMM Feb 06 '20 at 15:14
  • 1
    `operator[]` should bei `operator()` – Thomas Sablik Feb 06 '20 at 15:16
  • 3
    A map fundamentally sorts by keys. You can change how those keys are sorted via a comparator, but order has to be based on the key. Edit : If you want to sort by value and find keys, you probably want to swap what you consider a key and what you consider a value. – François Andrieux Feb 06 '20 at 15:16
  • 1
    IMHO, if you want to sort by key and by value, use `std::vector` and two maps. The first map will be , the second map will be . This is a database technique: all data in one table and one or more index tables. – Thomas Matthews Feb 06 '20 at 15:23
  • *I was trying to make a map sort by value using a custom comparator* -- Sounds like an [XY Problem](http://xyproblem.info/). What problem are you actually trying to solve? – PaulMcKenzie Feb 06 '20 at 15:28
  • Check the the docs or search on the web before asking here. [What are the basic rules and idioms for operator overloading?](https://stackoverflow.com/q/4421706/10147399) – Aykhan Hagverdili Feb 06 '20 at 15:28
  • @ThomasMatthews can you expand on that a little? I really like the idea but I don't understand how to use the vector or connect the keys and values to eachother – Hint33 Feb 06 '20 at 15:58
  • See my answer below. – Thomas Matthews Feb 06 '20 at 16:14

5 Answers5

1

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.

Thomas Sablik
  • 16,127
  • 7
  • 34
  • 62
1

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.

ChrisMM
  • 8,448
  • 13
  • 29
  • 48
1

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.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
0

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:

  1. 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; });
  1. 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.
theWiseBro
  • 1,439
  • 12
  • 11
0

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 ...

Thomas Matthews
  • 56,849
  • 17
  • 98
  • 154