map comparator for sorting map elements - c++

I wanted the elements of the map to be arranged in a specific sequence(shortest first).
So i wrote a simple comparator which compares the length of the elements being inserted in the map with the previous element(s).
struct cmpByStringLength {
bool operator()(const std::string& a, const std::string& b) const {
return a.length() < b.length();
}
};
int main()
{
map<string,string,cmpByStringLength> obj1;
obj1.insert(make_pair("Anurag","Last"));
obj1.insert(make_pair("Second","Last"));
for(map<string,string>::iterator it=obj1.begin();it!= obj1.end();++it)
{
cout<<it->first;
cout<<endl;
}
return 0;
}
But the above won't insert element with key as Second in the map since the comparator compares elements with keys Second with Anurag and they have equal length so doesn't insert element with key Second. However the following would work fine:
obj1.insert(make_pair("abc","Last"));
obj1.insert(make_pair("abcdefg","Last"));
obj1.insert(make_pair("abcd","Last"));
Turns out, my understanding about custom comparator to sort the elements in a map is wrong as it is used for inserting the elements and not for inserting it as per the sort logic i provided through comparator.
So in other words, is it correct to say that custom comparators are just used for deciding whether or not to insert an element at all in the map, and it is not used to decide where to place the element?

C++ documentation on map (and really anything using a std::less-style comparator) makes it quite clear that two elements a, b are equivalent iff !comp(a, b) && !comp(b, a) (see, for example, https://en.cppreference.com/w/cpp/container/map). This means that, yes, your comparator gets used for ordering and equivalence testing.
The way you'd usually fix this is to implement a two-level comparison, e.g.
return (a.length() == b.length()) ? (a < b) : (a.length() < b.length());

Using std::multimap can solve your problem. But I don't know size of your data so I can't comment how it will effect performance on your application.
#include <iostream>
#include <map>
using namespace std;
struct cmpByStringLength {
bool operator()(const std::string &a, const std::string &b) const {
return a.length() < b.length();
}
};
int main() {
multimap<string, string, cmpByStringLength> obj1;
obj1.insert(make_pair("Second1", "Last"));
obj1.insert(make_pair("Anurag", "Last"));
obj1.insert(make_pair("Second", "Last"));
obj1.insert(make_pair("Secon", "Last"));
for (map<string, string>::iterator it = obj1.begin(); it != obj1.end();
++it) {
cout << it->first;
cout << endl;
}
return 0;
}
Output
Secon
Anurag
Second
Second1

Related

How to sort a vector of strings in a specific predetermined order?

The problem: I need to sort a vector of strings in exact specific order. Let say we have a constant vector or a array with the exact order:
vector<string> correctOrder = {"Item3", "Item1", "Item5", "Item4", "Item2"};
Next, we have a dynamic incoming vector which will have same Items, but they maybe mixed and less in number.
vector<string> incommingVector = {"Item1", "Item5", "Item3"};
So I need to sort the incomming vector with the order like the first vector, correctOrder, and the result must be:
vector<string> sortedVector = {"Item3", "Item1", "Item5"};
I think the correct order may be represented in a different way, but can't figure out.
Can someone help me please?
If the default comparison is not enough (lexicographic comparison) then the simplest thing you can do is to provide the sort function with a lambda that tells it which string come first.
You can have a unordered_map<string,int> with the strings in your correctorder vector as keys and their corresponding position in the sorted array as values.
The cmp function will simply compare the values of the keys you provide in your incommingVector.
unordered_map<string, int> my_map;
for(int i = 0 ; i < correctorder.size() ; i++)
my_map[correctorder[i]]=i;
auto cmp =[&my_map](const string& s, const string& s1){
return my_map[s] < my_map[s1];
}
sort(incommingVector.begin(), incommingVector.end() , cmp);
You can create your own functor to sort your vector in template vector order as explained by below code :
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
struct MyComparator
{
//static const int x = 9;
const std::vector<std::string> correctOrder{"Item1", "Item2", "Item3", "Item4", "Item5"};
bool operator() (const std::string& first,const std::string& second )
{
auto firstitr = std::find(correctOrder.begin(),correctOrder.end(),first);
auto seconditr = std::find(correctOrder.begin(),correctOrder.end(),second);
return firstitr < seconditr;
}
};
void printVector(const std::vector<std::string>& input)
{
for(const auto&elem:input)
{
std::cout<<elem<<" , ";
}
std::cout<<std::endl;
}
int main()
{
std::vector<string> incomingVector = {"Item3", "Item5", "Item1"};
std::cout<<"vector before sort... "<<std::endl;
printVector(incomingVector);
std::sort(incomingVector.begin(),incomingVector.end(),MyComparator());
std::cout<<"vector after sort...."<<std::endl;
printVector(incomingVector);
return 0;
}
You can take advantage of std::unordered_map<std::string, int>, i.e., a hash table for mapping a string into an integer in constant time. You can use it for finding out the position that a given string occupies in your vector correctOrder in O(1), so that you can compare two strings that are in the vector incomming in constant time.
Consider the following function sort_incomming_vector():
#include <unordered_map>
using Vector = std::vector<std::string>;
void sort_incomming_vector(const Vector& correctOrder /*N*/, Vector& incomming /*M*/)
{
std::unordered_map<std::string, int> order;
// populate the order hash table in O(N) time
for (size_t i = 0; i < correctOrder.size(); ++i)
order[correctOrder[i]] = i;
// sort "incomming" in O(M*log M) time
std::sort(incomming.begin(), incomming.end(),
[&order](const auto& a, const auto& b) { // sorting criterion
return order[a] < order[b];
}
);
}
The hash table order maps the strings into integers, and this resulting integer is used by the lambda (i.e., the sorting criterion) passed to the sorting algorithm, std::sort, to compare a pair strings in the vector incomming, so that the sorting algorithm can permute them accordingly.
If correctOder contains N elements, and incomming contains M elements, then the hash table can be initialised in O(N) time, and incomming can be sorted in O(M*log M) time. Therefore, the whole algorithm will run in O(N + M*log M) time.
If N is much larger than M, this solution is optimal, since the dominant term will be N, i.e., O(N + M*log M) ~ O(N).
You need to create a comparison function that returns the correct ordering and pass that to std::sort. To do that, you can write a reusable function that returns a lambda that compares the result of trying to std::find the two elements being compared. std::find returns iterators, and you can compare those with the < operator.
#include <algorithm>
std::vector<std::string> correctOrder = {"Item1", "Item2", "Item3", "Item4", "Item5"};
// Could be just std::string correctOrder[], or std::array<...> etc.
// Returns a sorter that orders elements based on the order given by the iterator pair
// (so it supports not just std::vector<string> but other containers too.
template <typename ReferenceIter>
auto ordered_sorter(ReferenceIter ref_begin, ReferenceIter ref_end) {
// Note: you can build an std::unordered_map<ReferenceIter::value_type, std::size_t> to
// be more efficient and compare map.find(left)->second with
// map.find(right)->second (after you make sure the find does not return a
// one-past-the-end iterator.
return [&](const auto& left, const auto& right) {
return std::find(ref_begin, ref_end, left) < std::find(ref_begin, ref_end, right);
};
}
int main() {
using namespace std;
vector<string> v{"Item3", "Item5", "Item1"};
// Pass the ordered_sorter to std::sort
std::sort(v.begin(), v.end(), ordered_sorter(std::begin(correctOrder), std::end(correctOrder)));
for (const auto& s : v)
std::cout << s << ", "; // "Item1, Item3, Item5, "
}
Note that this answer less efficient with a large number of elements, but more simpler than the solutions using an std::unordered_map<std::string, int> for lookup, but a linear search is probably faster for small number of elements. Do your benchmarking if performance matters.
Edit: If you don't want the default comparison to be used, then you need to pass as a third parameter your custom compare method, as shown in the example that exists in the linked reference.
Use std::sort and you are done:
#include <iostream> // std::cout
#include <algorithm> // std::sort
#include <vector> // std::vector
#include <string> // std::string
using namespace std;
int main () {
vector<string> incommingVector = {"Item3", "Item5", "Item1"};
// using default comparison (operator <):
std::sort (incommingVector.begin(), incommingVector.end());
// print out content:
std::cout << "incommingVector contains:";
for (std::vector<string>::iterator it=incommingVector.begin(); it!=incommingVector.end(); ++it)
std::cout << ' ' << *it;
std::cout << '\n';
return 0;
}
Output:
incommingVector contains: Item1 Item3 Item5

Why does STL Set overwrite pairs with same value

I can sort an unordered map by value in descending order using this answer.
However, using a set for the same job fails:
#include <set>
#include <functional>
#include <iostream>
using namespace std;
typedef pair<string, int> Pair;
typedef function<bool(Pair, Pair)> Comparator;
Comparator DescendingSortComparator = [](Pair pair1, Pair pair2) {
return pair1.second > pair2.second;
};
void SortHashTableByValueDescending(unordered_map<string, int> hashTable) {
set<Pair, Comparator> orderedSet(hashTable.begin(), hashTable.end(), DescendingSortComparator);
for (auto element : orderedSet)
cout << element.first << ": " << element.second << endl;
}
Running with the following test:
void Test_SortMap()
{
unordered_map<string, int> CountTable;
CountTable["word"] = 1;
CountTable["spark"] = 15;
CountTable["the"] = 2;
CountTable["mail"] = 3;
CountTable["info"] = 3;
CountTable["sandwich"] = 15;
SortHashTableByValueDescending(CountTable);
}
yiels the following output:
spark: 15
info: 3
the: 2
word: 1
Can anyone please tell me why set (probably) overwrites pairs with same value? The keys for such pairs are distinct anyways.
See the definition of Compare function of std::set.
Everywhere the standard library uses the Compare concept, uniqueness is determined by using the equivalence relation. In imprecise terms, two objects a and b are considered equivalent if neither compares less than the other: !comp(a, b) && !comp(b, a).
This means that equal numbers will considered equivalent and not copied into your orderedSet
Use
Comparator DescendingSortComparator = [](Pair pair1, Pair pair2) {
if (pair1.second == pair2.second)
return pair1.first > pair2.first;
else return pair1.second > pair2.second;
};
if you want to keep them
From the cppreference.com:
std::set is an associative container that contains a sorted set of
unique objects of type Key.
According to your Comparator only a single std::pair with a fixed second element can be stored in the set.

How not to use custom comparison function of std::map in searching ( map::find)?

As you can see in my code, lenMap is a std::map with a custom comparison function. This function just check the string's length.
Now when I want to search for some key ( using map::find), the map still uses that custom comparison function.
But How can I force my map not to use that when I search for some key ?
Code:
struct CompareByLength : public std::binary_function<string, string, bool>
{
bool operator()(const string& lhs, const string& rhs) const
{
return lhs.length() < rhs.length();
}
};
int main()
{
typedef map<string, string, CompareByLength> lenMap;
lenMap mymap;
mymap["one"] = "one";
mymap["a"] = "a";
mymap["foobar"] = "foobar";
// Now In mymap: [a, one, foobar]
string target = "b";
if (mymap.find(target) == mymap.end())
cout << "Not Found :) !";
else
cout << "Found :( !"; // I don't want to reach here because of "a" item !
return 0;
}
The map itself does not offer such an operation. The idea of the comparison functor is to create an internal ordering for faster lookup, so the elements are actually ordered according to your functor.
If you need to search for elements in a different way, you can either use the STL algorithm std::find_if() (which has linear time complexity) or create a second map that uses another comparison functor.
In your specific example, since you seem only to be interested in the string's length, you should rather use the length (of type std::size_t) and not the string itself as a key.
By the way, std::binary_function is not needed as a base class. Starting from C++11, it has even been deprecated, see here for example.
The comparison function tells the map how to order elements and how to differentiate between them. If it only compares the length, two different strings with the same length will occupy the same position in the map (one will overwrite the other).
Either store your strings in a different data structure and sort them, or perhaps try this comparison function:
struct CompareByLength
{
bool operator()(const string& lhs, const string& rhs) const
{
if (lhs.length() < rhs.length())
{
return true;
}
else if (rhs.length() < lhs.length())
{
return false;
}
else
{
return lhs < rhs;
}
}
};
I didn't test it, but I believe this will first order strings by length, and then however strings normally compare.
You could also use std::map<std::string::size_type, std::map<std::string, std::string>> and use the length for the first map and the string value for the second map. You would probably want to wrap this in a class to make it easier to use, as there is no protection against messing it up.

How to sort only subset in std::vector?

I have a vector of pairs:
std::vector<std::pair<std::string, Cell::Ptr>> mCells;
I want to sort only a subset of elements (on the first's string). The Cell has method GetSorted() which indicates if it's part of this subset or not.
This is what I had initially:
std::sort(mCells.begin(), mCells.end(),
[](std::pair<std::string, Cell::Ptr> const &a,
std::pair<std::string, Cell::Ptr> const &b)
{
// Only compare when both cells need to be sorted; otherwise return false
// to indicate that they are already in correct order. This keeps the
// non-marked cells at their original positions.
if (a.second->GetSorted() && b.second->GetSorted())
{
return a.first < b.first;
}
else
{
return false;
}
});
But it does not work, because sort, of course, does not compare all combinations. Sometimes the return a.first < b.first line is not even executed once.
To define the required sort function, here's an example. Suppose the elements are:
G* F C* A* D B E*
Only the *-ones need to be sorted. But, the sort should only be applied to adjacent to-be-sorted elements. (That's why I had a.second->GetSorted() && b.second->GetSorted().) The result should then be:
G* F A* C* D B E*
So, only A and C are adjacent, and are sorted. Is there an easy solution to this problem?
Alternatively a solution that results in:
A* F C* E* D B G*
would also be usable for me at the moment. So, sorting all * elements, while leaving the others where they are. This appears to be easier to do.
You need to separate finding the ranges to be sorted and sorting them:
using namespace std;
auto isSorted = [](std::pair<std::string, Cell::Ptr> const &a) {
return a.second->GetSorted();
}
auto it = begin(mCells);
const auto itEnd = end(mCells);
while (it != itEnd) {
auto rangeStart = find_if(it, itEnd, isSorted);
if (rangeStart == itEnd)
break;
auto rangeEnd = find_if_not(rangeStart, itEnd, isSorted);
if (distance(rangeStart, rangeEnd) > 1) {
// pair comparison should do the trick here
sort(rangeStart, rangeEnd);
}
it = rangeEnd;
}
Just saw your edit: you can achieve the alternate solution by defining a custom input iterator class that skips non-sorted elements, then using a single sort() call on the whole "range".

Sort List based on dynamically generated numbers in C++

I have a list of objects ("Move"'s in this case) that I want to sort based on their calculated evaluation. So, I have the List, and a bunch of numbers that are "associated" with an element in the list. I now want to sort the List elements with the first element having the lowest associated number, and the last having the highest. Once the items are order I can discard the associated number. How do I do this?
This is what my code looks like (kind've):
list<Move> moves = board.getLegalMoves(board.turn);
for(i = moves.begin(); i != moves.end(); ++i)
{
//...
a = max; // <-- number associated with current Move
}
I would suggest a Schwartzian transform sort. Make a new vector (I recommend vector for more efficient sorting) of pairs of the associated value, and a pointer to its item. Sort the vector of pairs and then regenerate the list from the sorted vector. Since operator< is defined on a std::pair to be comparison by the first item of the pair and then the second, you will get a proper ordering.
Example:
#include <algorithm> // gives you std::sort
#include <utility> // gives you std::pair
typedef double CostType;
typedef std::pair<CostType, Move*> Pair;
// Create the vector of pairs
std::vector<Pair> tempVec;
tempVec.reserve(moves.size());
for (std::list<Move>::iterator i = moves.begin(); i != moves.end(); ++i)
{
CostType cost = calcCost(*i);
Move* ptrToI = &(*i);
tempVec.push_back(Pair(cost, ptrToI));
}
// Now sort 'em
std::sort(tempVec.begin(), tempVec.end());
// Regenerate your original list in sorted order by copying the original
// elements from their pointers in the Pair.
std::list<Move> sortedMoves;
for (std::vector<Pair>::iterator i = tempVec.begin(); i != tempVec.end(); ++i)
{
sortedMoves.push_back(*(i->second));
}
Note that you will need a calcCost function that I have assumed here. This approach has an advantage over creating a comparison function if your comparison value calculation is time consuming. This way, you only pay the cost for calculating the comparison N times instead of 2 * N * log(N).
You could make a comparison function that compares the two elements in the way that you would like.
bool compare_m (const Move &first,const Move &second)
{
if (first.thing_you_are_comparing_on() < second.thing_you_are_comparing_on()) return true;
else return false;
}
Where "thing_you_are_comparing_on" is some member of the Move class that gives you the ordering you want. We use const here to make sure that we are only comparing and not actually changing the objects in the comparison function. You can then call the sort method on the list with compare_m as the comparison function:
moves.sort(compare_m)
Something to note is that if the calculation of the comparison function is particularly expensive it may be worthwhile to precompute all the associated rank numbers before sorting.
This would require adding something to the move class to store the rank for use later:
class Move{
//rest of move class
public:
int rank;
};
list<Move>::iterator iter;
for(iter = moves.begin(); iter != moves.end(); ++iter)
{
//...
(*iter).rank = max; // store the number associated with current Move
}
bool compare_rank (const Move &first,const Move &second)
{
if (first.rank < second.rank) return true;
else return false;
}
std::sort is used to sort STL collections. If the elements in the collection you are sorting can be compared simply by calling operator< and the collection in question is a vector, then sorting is very simple:
std::sort(collection.begin(), collection.end());
If the collection in question is not a vector but a list as in your case, then you can't use the general version of std::sort, but you can use std::list's version instead:
list<int> numbers;
numbers.sort();
STL's sort, along with most other algorithms in the STL, come in two flavors. One is the simple version we have already seen, which just uses operator< to do the comparison of two elements. The other is a 'predicated' version, which instead of using operator< uses a comparison functor you provide. This is what you need to use in your case. There is a predicated version of sort for list, and this is what you need to use in your case.
You can create a functor in a number of ways, but one of the most useful is to derive a class from std::unary_function or from std::binary_function, depending on how many arguments your functor will take -- in your case, two. Override the function-call operator, operator() and add the code that compares two elements:
class compare_functor : public std::binary_function<Move, Move, bool>
{
public:
bool operator(const Move& lhs, const Move& rhs) const
{
int left_val = lhs.Value();
int right_val = rhs.Value();
return left_val < right_val;
};
Here is a complete working example that puts everything together. In this program, instead of having a list of Moves, I have a list of 10 strings. Each string is 6 random characters. The list is populated by the call to generate_n, which uses the functor generator to create each random string. Then I dump that list of strings, along with their values, by calling copy and passing an output iterator that dumps the values to stdout (ostream_iterator). The value of each string is simply a sum of the numeric value of each character, computed by the function strng_val.
Then I sort the list using list's predicated version of sort. The comparison predicate used by sort is evaluator. Then I finally dump the resulting list and the string values to the screen again as above:
#include <cstdlib>
#include <iostream>
#include <list>
#include <string>
#include <algorithm>
#include <ctime>
#include <sstream>
using namespace std;
class generator
{
public:
generator() { srand((unsigned)time(0)); }
string operator()() const
{
string ret;
for( int i = 0; i < 6; ++i )
ret += static_cast<char>((rand()/(RAND_MAX/26)) + 'A');
return ret;
}
};
unsigned string_val(const string& rhs)
{
unsigned val = 0;
for( string::const_iterator it = rhs.begin(); it != rhs.end(); ++it )
val += (*it)-'A'+1;
return val;
};
class evaluator : public std::binary_function<string,string,bool>
{
public:
bool operator()(const string& lhs, const string& rhs) const
{
return string_val(lhs) < string_val(rhs);
}
};
class string_dumper : public std::unary_function<string, string>
{
public:
string operator()(const string& rhs) const
{
stringstream ss;
ss << rhs << " = " << string_val(rhs);
return ss.str();
}
};
int main()
{
// fill a list with strings of 6 random characters
list<string> strings;
generate_n(back_inserter(strings), 10, generator());
// dump it to the screen
cout << "Unsorted List:\n";
transform(strings.begin(), strings.end(), ostream_iterator<string>(cout, "\n"), string_dumper());
// sort the strings according to their numeric values computed by 'evaluator'
strings.sort(evaluator()); // because this is a 'list', we are using list's 'sort'
// dump it to the screen
cout << "\n\nSorted List:\n";
transform(strings.begin(), strings.end(), ostream_iterator<string>(cout, "\n"), string_dumper());
return 0;
}