C++ map customize comparator - c++

I defined a map to count the number of strings while sorting the strings by their length:
struct cmp {
bool operator()(const string& a, const string& b) {
return a.size() > b.size();
}
};
int main() {
map<string, int, cmp> mp;
mp["aaa"] = 1;
mp["bbb"] = 2;
cout << mp["aaa"];
}
I'm confused as the output is 2. How should I achieve my goal?

Because of the way your comparator is defined, strings "aaa" and "bbb" are considered equal. Your map has one item, not two. First you assigned 1 to that item, then you assigned 2.
To solve the problem, define your comparator as follows:
struct cmp {
bool operator()(const string& a, const string& b) {
return a.size() == b.size() ? a > b : a.size() > b.size();
}
};
That way, the strings will be considered equal only if they actually are equal, not only when their sizes match, but the string length will still have priority for sorting.

std::map not only sorts items by key, it stores them by (unique) key - 1 item per key.
This behavior is defined by the comparator: if for keys a & b neither of a<b and b<a is true, these keys are considered equal.
In your case mp["bbb"] = 2 just overwrites mp["aaa"].
If you want to fit all the strings in the map, you can use std::multimap, which allows more than 1 value per key.
The other way is to redefine the comparator, so that it would take the different strings into account:
struct cmp {
bool operator()(const string& a, const string& b) {
if(a.size() < b.size()) return true;
if(b.size() < a.size()) return false;
return a<b;
}
};
Thus your map will still prioritize sorting by string length, but it will also distinguish different strings of same size.
Depending on your use case, you can also check other containers like priority_queue or just plain vector with a proper insertion technique.

If you want to allow strings of identical size in your map but do not care about their relative order, then std::multimap is an alternative solution:
#include <map>
#include <iostream>
#include <string>
struct cmp {
bool operator()(const std::string& a, const std::string& b) const {
return a.size() > b.size();
}
};
int main() {
std::multimap<std::string, int, cmp> mp;
mp.emplace("eee", 5);
mp.emplace("aaa", 1);
mp.emplace("bbb", 2);
mp.emplace("cccc", 3);
mp.emplace("dd", 4);
auto const elements_of_size_3 = mp.equal_range("aaa");
for (auto iter = elements_of_size_3.first; iter != elements_of_size_3.second; ++iter)
{
std::cout << iter->first << " -> " << iter->second << '\n';
}
}
Output:
eee -> 5
aaa -> 1
bbb -> 2
From std::multimap<std::string, int, cmp>'s point of view, "eee", "aaa" and "bbb" are all completely equal to each other, and std::multimap allows different keys to be equal. Their relative order is actually guaranteed to be the order of insertion since C++11.

Related

Difference in order with same comparator in Priority Queue & Vector

Following code uses same comparator function with vector and priority queue. However order produced by both data structures is different. I would like priority queue to behave in same way as vector.
I have two questions
Why is order different?
How can i make priority queue's order to be same as vector?
Here's the output of following code:
//Please ignore extra header files, I know I don't need them.
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#include <queue>
#include <stack>
#include <iterator>
#include <unordered_map>
#include <functional>
using namespace std;
class Solution {
public:
typedef pair<string, int> PII;
static bool cmp(const PII& a, const PII& b)
{
if (a.second == b.second)
return a.first < b.first;
return a.second > b.second;
}
void func(vector<string>& words)
{
unordered_map<string, int> hMap;
for (const auto& w : words)
hMap[w]++;
std::priority_queue< PII, std::vector<PII>, std::function<bool(PII, PII)> > Q(cmp);
vector<PII> V;
for (const auto& e : hMap)
{
Q.emplace(e);
V.emplace_back(e);
}
std::sort(V.begin(), V.end(), cmp);
//Now why does order of elements is different in vector V and priority_queue Q, despite using same comparator function?
int size = Q.size();
cout << "Order in priority Queue:" << endl;
for (int i = 0; i < size; i++)
{
PII e = Q.top();
cout << e.first << ":" << e.second << endl;
Q.pop();
}
cout << "Order in vector:" << endl;
for (const auto& e : V)
{
cout << e.first << ":" << e.second << endl;
}
}
};
int main()
{
Solution s;
vector<string> words = {"the", "day", "is", "sunny", "the", "the", "the", "sunny", "is", "is" , "we" , "we" , "we" };
s.func( words );
return 0;
}
The order is different because < relation implies that std::sort sorts values in ascending order, and that std::priority_queue places the maximum element at the top. This is by design.
If you want to reverse the order in the priority queue, you need another comparator that swaps the arguments,
bool cmp2(const T& a, const T& b) {
return cmp(b, a);
}
//...
std::priority_queue<T, std::vector<T>, decltype(&cmp2)> queue(cmp2);
This is in perfect analogy with going from std::less to std::greater as explained in this question.
Instead of introducing a separate function, you can use a lambda:
auto cmp2 = [](const auto& a, const auto& b) { return cmp(b, a); };
std::priority_queue<T, std::vector<T>, decltype(cmp2)> queue(cmp2);
Priority Queues and vectors use comparators differently. To understand the output of priority queue, you must understand its working. Priority Queue is effectively a heap with an element on top depending on the comparison function. To quote from boost Priority Queue
The comparison function used to determine whether one element is
smaller than another element. If Compare(x,y) is true, then x is
smaller than y. The element returned by Q.top() is the largest element
in the priority queue. That is, it has the property that, for every
other element x in the priority queue, Compare(Q.top(), x) is false.
In your case changing the comparison function to reverse the order should solve the issue.

Proper value for unordered_map

I have a set of strings that I have to put into a hash table and retrieve anagrams of it. I chose the unordered_map since it's an inbuilt hash table in c++. The strings are as followings,
cat, act, art, tar, rat... etc..
Now I used the alphabetically sorted word as key and a vector of unordered words as value. This implementation takes a lot of time in insertion. Is this the best possible implementation for the requirement, or is there something less time consuming that I can use?
std::tr1::unordered_map<std::string, std::vector<std::string>> map;
if(map.find(sortedWord) == map.end()){
std::vector<std::string> temp;
temp.push_back(word);
map.insert(make_pair(sortedWord, temp));
}else{
map.find(sortedWord)->second.push_back(word);
}
You're making that a lot more complicated than necessary, and in the process you're also slowing things down by:
Searching for the key twice, and
Copying a vector (with the contained word) when you have a new key. In fact, it is probably copied twice.
The following works fine with C++11, and I'm pretty sure it works the same way with tr1:
/* Name of map changed because I don't like to use "map"
* or "string" as variable names */
wordMap[sortedWord].push_back(word);
If sortedWord is not present in the map, then wordMap[sortedWord] will insert it with a default-constructed std::vector<std::string>>. So whether or not sortedWord was present, the new word can just be appended onto the value returned by the subscript.
Just to offer another solution, you may use C++11 std::unordered_multiset with customized hash algorithm and equality comparison.
The custom hash algorithm may simply combine the hash values of each characters with a commutative operation, say bitwise-xor, such that all anagrams have the same hash value.
The custom equality comparison can use std::is_permutation to equate all anagrams.
struct AnagramHash
{
typedef std::string argument_type;
typedef std::hash<char>::result_type result_type;
result_type operator()(const argument_type& s) const
{
std::hash<char> char_hash;
result_type result = 0;
for (const auto& x : s)
{
result ^= char_hash(x);
}
return result;
}
};
 
struct AnagramEqual
{
typedef bool result_type;
typedef std::string first_argument_type;
typedef std::string second_argument_type;
result_type operator()(const first_argument_type& lhs, const second_argument_type& rhs) const
{
if (lhs.size() == rhs.size())
return std::is_permutation(std::begin(lhs), std::end(lhs), std::begin(rhs));
else
return false;
}
};
 
int main()
{
std::unordered_multiset<std::string, AnagramHash, AnagramEqual> anagrams;
anagrams.insert("arc");
anagrams.insert("rac");
anagrams.insert("car");
anagrams.insert("H2O");
anagrams.insert("2OH");
auto range = anagrams.equal_range("car");
for (auto it = range.first ; it != range.second ; ++it)
{
cout << *it << endl;
}
cout << endl;
range = anagrams.equal_range("HO2");
for (auto it = range.first ; it != range.second ; ++it)
{
cout << *it << endl;
}
}

Sorting one std::vector based on the content of another [duplicate]

This question already has answers here:
How can I sort two vectors in the same way, with criteria that uses only one of the vectors?
(9 answers)
Closed 9 months ago.
I have several std::vector, all of the same length. I want to sort one of these vectors, and apply the same transformation to all of the other vectors. Is there a neat way of doing this? (preferably using the STL or Boost)? Some of the vectors hold ints and some of them std::strings.
Pseudo code:
std::vector<int> Index = { 3, 1, 2 };
std::vector<std::string> Values = { "Third", "First", "Second" };
Transformation = sort(Index);
Index is now { 1, 2, 3};
... magic happens as Transformation is applied to Values ...
Values are now { "First", "Second", "Third" };
friol's approach is good when coupled with yours. First, build a vector consisting of the numbers 1…n, along with the elements from the vector dictating the sorting order:
typedef vector<int>::const_iterator myiter;
vector<pair<size_t, myiter> > order(Index.size());
size_t n = 0;
for (myiter it = Index.begin(); it != Index.end(); ++it, ++n)
order[n] = make_pair(n, it);
Now you can sort this array using a custom sorter:
struct ordering {
bool operator ()(pair<size_t, myiter> const& a, pair<size_t, myiter> const& b) {
return *(a.second) < *(b.second);
}
};
sort(order.begin(), order.end(), ordering());
Now you've captured the order of rearrangement inside order (more precisely, in the first component of the items). You can now use this ordering to sort your other vectors. There's probably a very clever in-place variant running in the same time, but until someone else comes up with it, here's one variant that isn't in-place. It uses order as a look-up table for the new index of each element.
template <typename T>
vector<T> sort_from_ref(
vector<T> const& in,
vector<pair<size_t, myiter> > const& reference
) {
vector<T> ret(in.size());
size_t const size = in.size();
for (size_t i = 0; i < size; ++i)
ret[i] = in[reference[i].first];
return ret;
}
typedef std::vector<int> int_vec_t;
typedef std::vector<std::string> str_vec_t;
typedef std::vector<size_t> index_vec_t;
class SequenceGen {
public:
SequenceGen (int start = 0) : current(start) { }
int operator() () { return current++; }
private:
int current;
};
class Comp{
int_vec_t& _v;
public:
Comp(int_vec_t& v) : _v(v) {}
bool operator()(size_t i, size_t j){
return _v[i] < _v[j];
}
};
index_vec_t indices(3);
std::generate(indices.begin(), indices.end(), SequenceGen(0));
//indices are {0, 1, 2}
int_vec_t Index = { 3, 1, 2 };
str_vec_t Values = { "Third", "First", "Second" };
std::sort(indices.begin(), indices.end(), Comp(Index));
//now indices are {1,2,0}
Now you can use the "indices" vector to index into "Values" vector.
Put your values in a Boost Multi-Index container then iterate over to read the values in the order you want. You can even copy them to another vector if you want to.
Only one rough solution comes to my mind: create a vector that is the sum of all other vectors (a vector of structures, like {3,Third,...},{1,First,...}) then sort this vector by the first field, and then split the structures again.
Probably there is a better solution inside Boost or using the standard library.
You can probably define a custom "facade" iterator that does what you need here. It would store iterators to all your vectors or alternatively derive the iterators for all but the first vector from the offset of the first. The tricky part is what that iterator dereferences to: think of something like boost::tuple and make clever use of boost::tie. (If you wanna extend on this idea, you can build these iterator types recursively using templates but you probably never want to write down the type of that - so you either need c++0x auto or a wrapper function for sort that takes ranges)
I think what you really need (but correct me if I'm wrong) is a way to access elements of a container in some order.
Rather than rearranging my original collection, I would borrow a concept from Database design: keep an index, ordered by a certain criterion. This index is an extra indirection that offers great flexibility.
This way it is possible to generate multiple indices according to different members of a class.
using namespace std;
template< typename Iterator, typename Comparator >
struct Index {
vector<Iterator> v;
Index( Iterator from, Iterator end, Comparator& c ){
v.reserve( std::distance(from,end) );
for( ; from != end; ++from ){
v.push_back(from); // no deref!
}
sort( v.begin(), v.end(), c );
}
};
template< typename Iterator, typename Comparator >
Index<Iterator,Comparator> index ( Iterator from, Iterator end, Comparator& c ){
return Index<Iterator,Comparator>(from,end,c);
}
struct mytype {
string name;
double number;
};
template< typename Iter >
struct NameLess : public binary_function<Iter, Iter, bool> {
bool operator()( const Iter& t1, const Iter& t2 ) const { return t1->name < t2->name; }
};
template< typename Iter >
struct NumLess : public binary_function<Iter, Iter, bool> {
bool operator()( const Iter& t1, const Iter& t2 ) const { return t1->number < t2->number; }
};
void indices() {
mytype v[] = { { "me" , 0.0 }
, { "you" , 1.0 }
, { "them" , -1.0 }
};
mytype* vend = v + _countof(v);
Index<mytype*, NameLess<mytype*> > byname( v, vend, NameLess<mytype*>() );
Index<mytype*, NumLess <mytype*> > bynum ( v, vend, NumLess <mytype*>() );
assert( byname.v[0] == v+0 );
assert( byname.v[1] == v+2 );
assert( byname.v[2] == v+1 );
assert( bynum.v[0] == v+2 );
assert( bynum.v[1] == v+0 );
assert( bynum.v[2] == v+1 );
}
A slightly more compact variant of xtofl's answer for if you are just looking to iterate through all your vectors based on the of a single keys vector. Create a permutation vector and use this to index into your other vectors.
#include <boost/iterator/counting_iterator.hpp>
#include <vector>
#include <algorithm>
std::vector<double> keys = ...
std::vector<double> values = ...
std::vector<size_t> indices(boost::counting_iterator<size_t>(0u), boost::counting_iterator<size_t>(keys.size()));
std::sort(begin(indices), end(indices), [&](size_t lhs, size_t rhs) {
return keys[lhs] < keys[rhs];
});
// Now to iterate through the values array.
for (size_t i: indices)
{
std::cout << values[i] << std::endl;
}
ltjax's answer is a great approach - which is actually implemented in boost's zip_iterator http://www.boost.org/doc/libs/1_43_0/libs/iterator/doc/zip_iterator.html
It packages together into a tuple whatever iterators you provide it.
You can then create your own comparison function for a sort based on any combination of iterator values in your tuple. For this question, it would just be the first iterator in your tuple.
A nice feature of this approach is that it allows you to keep the memory of each individual vector contiguous (if you're using vectors and that's what you want). You also don't need to store a separate index vector of ints.
This would have been an addendum to Konrad's answer as it an approach for a in-place variant of applying the sort order to a vector. Anyhow since the edit won't go through I will put it here
Here is a in-place variant with a slightly higher time complexity that is due to a primitive operation of checking a boolean. The additional space complexity is of a vector which can be a space efficient compiler dependent implementation. The complexity of a vector can be eliminated if the given order itself can be modified.
Here is a in-place variant with a slightly higher time complexity that is due to a primitive operation of checking a boolean. The additional space complexity is of a vector which can be a space efficient compiler dependent implementation. The complexity of a vector can be eliminated if the given order itself can be modified. This is a example of what the algorithm is doing.
If the order is 3 0 4 1 2, the movement of the elements as indicated by the position indices would be 3--->0; 0--->1; 1--->3; 2--->4; 4--->2.
template<typename T>
struct applyOrderinPlace
{
void operator()(const vector<size_t>& order, vector<T>& vectoOrder)
{
vector<bool> indicator(order.size(),0);
size_t start = 0, cur = 0, next = order[cur];
size_t indx = 0;
T tmp;
while(indx < order.size())
{
//find unprocessed index
if(indicator[indx])
{
++indx;
continue;
}
start = indx;
cur = start;
next = order[cur];
tmp = vectoOrder[start];
while(next != start)
{
vectoOrder[cur] = vectoOrder[next];
indicator[cur] = true;
cur = next;
next = order[next];
}
vectoOrder[cur] = tmp;
indicator[cur] = true;
}
}
};
Here is a relatively simple implementation using index mapping between the ordered and unordered names that will be used to match the ages to the ordered names:
void ordered_pairs()
{
std::vector<std::string> names;
std::vector<int> ages;
// read input and populate the vectors
populate(names, ages);
// print input
print(names, ages);
// sort pairs
std::vector<std::string> sortedNames(names);
std::sort(sortedNames.begin(), sortedNames.end());
std::vector<int> indexMap;
for(unsigned int i = 0; i < sortedNames.size(); ++i)
{
for (unsigned int j = 0; j < names.size(); ++j)
{
if (sortedNames[i] == names[j])
{
indexMap.push_back(j);
break;
}
}
}
// use the index mapping to match the ages to the names
std::vector<int> sortedAges;
for(size_t i = 0; i < indexMap.size(); ++i)
{
sortedAges.push_back(ages[indexMap[i]]);
}
std::cout << "Ordered pairs:\n";
print(sortedNames, sortedAges);
}
For the sake of completeness, here are the functions populate() and print():
void populate(std::vector<std::string>& n, std::vector<int>& a)
{
std::string prompt("Type name and age, separated by white space; 'q' to exit.\n>>");
std::string sentinel = "q";
while (true)
{
// read input
std::cout << prompt;
std::string input;
getline(std::cin, input);
// exit input loop
if (input == sentinel)
{
break;
}
std::stringstream ss(input);
// extract input
std::string name;
int age;
if (ss >> name >> age)
{
n.push_back(name);
a.push_back(age);
}
else
{
std::cout <<"Wrong input format!\n";
}
}
}
and:
void print(const std::vector<std::string>& n, const std::vector<int>& a)
{
if (n.size() != a.size())
{
std::cerr <<"Different number of names and ages!\n";
return;
}
for (unsigned int i = 0; i < n.size(); ++i)
{
std::cout <<'(' << n[i] << ", " << a[i] << ')' << "\n";
}
}
And finally, main() becomes:
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <algorithm>
void ordered_pairs();
void populate(std::vector<std::string>&, std::vector<int>&);
void print(const std::vector<std::string>&, const std::vector<int>&);
//=======================================================================
int main()
{
std::cout << "\t\tSimple name - age sorting.\n";
ordered_pairs();
}
//=======================================================================
// Function Definitions...
**// C++ program to demonstrate sorting in vector
// of pair according to 2nd element of pair
#include <iostream>
#include<string>
#include<vector>
#include <algorithm>
using namespace std;
// Driver function to sort the vector elements
// by second element of pairs
bool sortbysec(const pair<char,char> &a,
const pair<int,int> &b)
{
return (a.second < b.second);
}
int main()
{
// declaring vector of pairs
vector< pair <char, int> > vect;
// Initialising 1st and 2nd element of pairs
// with array values
//int arr[] = {10, 20, 5, 40 };
//int arr1[] = {30, 60, 20, 50};
char arr[] = { ' a', 'b', 'c' };
int arr1[] = { 4, 7, 1 };
int n = sizeof(arr)/sizeof(arr[0]);
// Entering values in vector of pairs
for (int i=0; i<n; i++)
vect.push_back( make_pair(arr[i],arr1[i]) );
// Printing the original vector(before sort())
cout << "The vector before sort operation is:\n" ;
for (int i=0; i<n; i++)
{
// "first" and "second" are used to access
// 1st and 2nd element of pair respectively
cout << vect[i].first << " "
<< vect[i].second << endl;
}
// Using sort() function to sort by 2nd element
// of pair
sort(vect.begin(), vect.end(), sortbysec);
// Printing the sorted vector(after using sort())
cout << "The vector after sort operation is:\n" ;
for (int i=0; i<n; i++)
{
// "first" and "second" are used to access
// 1st and 2nd element of pair respectively
cout << vect[i].first << " "
<< vect[i].second << endl;
}
getchar();
return 0;`enter code here`
}**
with C++11 lambdas and the STL algorithms based on answers from Konrad Rudolph and Gabriele D'Antona:
template< typename T, typename U >
std::vector<T> sortVecAByVecB( std::vector<T> & a, std::vector<U> & b ){
// zip the two vectors (A,B)
std::vector<std::pair<T,U>> zipped(a.size());
for( size_t i = 0; i < a.size(); i++ ) zipped[i] = std::make_pair( a[i], b[i] );
// sort according to B
std::sort(zipped.begin(), zipped.end(), []( auto & lop, auto & rop ) { return lop.second < rop.second; });
// extract sorted A
std::vector<T> sorted;
std::transform(zipped.begin(), zipped.end(), std::back_inserter(sorted), []( auto & pair ){ return pair.first; });
return sorted;
}
So many asked this question and nobody came up with a satisfactory answer. Here is a std::sort helper that enables to sort two vectors simultaneously, taking into account the values of only one vector. This solution is based on a custom RadomIt (random iterator), and operates directly on the original vector data, without temporary copies, structure rearrangement or additional indices:
C++, Sort One Vector Based On Another One

Maps and substrings

I want to sort suffices of a string.
The most simple way to do that is putting all the suffices into map.
In order to use memory efficiently, I pass suffix as (str+i), where str is char* and i is a position suffix starts with. However, I found out that map is not going to sort these suffices. Here goes an example
typedef std::map < char*, int,Comparator> MapType;
MapType data;
// let's declare some initial values to this map
char* bob=(char* )"Bobs score";
char* marty=(char* ) "Martys score";
data.insert(pair<char*,int>(marty+1,15));
data.insert(pair<char*,int>(bob+1,10));
MapType::iterator end = data.end();
for (MapType::iterator it = data.begin(); it != end; ++it) {
std::cout << "Who(key = first): " << it->first;
std::cout << " Score(value = second): " << it->second << '\n';
}
The output is
Who(key = first): obs score Score(value = second): 10
Who(key = first): artys score Score(value = second): 15
However, strcmp, standard function for comparing strings, works correctly for bob+1 and marty+1. It says marty+1 is less than bob+1.
The map will sort by the address of the char*, not lexiographically. Change the key to a std::string or define a comparator.
EDIT:
It looks as though you have attempted to define a Comparator but the definition of it is not posted. Here is an example:
#include <iostream>
#include <map>
#include <string.h>
struct cstring_compare
{
bool operator()(const char* a_1, const char* a_2) const
{
return strcmp(a_1, a_2) < 0;
}
};
typedef std::map<const char*, int, cstring_compare> cstring_map;
int main()
{
cstring_map m;
m["bcd"] = 1;
m["acd"] = 1;
m["abc"] = 1;
for (cstring_map::iterator i = m.begin(); i != m.end(); i++)
{
std::cout << i->first << "\n";
}
return 0;
}
Output:
abc
acd
bcd
define a custom Comparator, eg
class compare_char {
public:
bool operator()(const char* lhs, const char* rhs) { return strcmp(lhs, rhs); }
};
define your map using this comparator instead of whatever you currently have. Alternatively, use a map with a key type that has a comparison operator that works with values, a std::string is better for you. Currently you have a map using char* as the key which compares char* types, ie. the value of the pointer, not the contents.
You should add the comparer class or function you are using since that is where your error is probably coming from.
There is a slight difference between strcmp and a map comparaison function.
strcmp returns 0 if a == b, -1 if a < b, 1 if a > b
comp returns true is a < b, false otherwise.
A correct way to implement the comparison function is the following:
bool operator() (char* lhs, char* rhs) const
{
return strcmp(lhs,rhs) < 0;
}

Sorting a set<string> on the basis of length

My question is related to this.
I wanted to perform a sort() operation over the set with the help of a lambda expression as a predicate.
My code is
#include <set>
#include <string>
#include <iostream>
#include <algorithm>
int main() {
using namespace std;
string s = "abc";
set<string> results;
do {
for (int n = 1; n <= s.size(); ++n) {
results.insert(s.substr(0, n));
}
} while (next_permutation(s.begin(), s.end()));
sort (results.begin(),results.end());[](string a, string b)->bool{
size_t alength = a.length();
size_t blength = b.length();
return (alength < blength);
});
for (set<string>::const_iterator x = results.begin(); x != results.end(); ++x) {
cout << *x << '\n';
}
return 0;
}
But the numbers and types of errors were so complex that I couldn't understand how to fix them. Can someone tell me whats wrong with this code.
Edit: Note that Steve Townsend's solution is actually the one you're searching for, as he inlines as a C++0x Lambda what I write as C++03 code below.
Another solution would be to customize the std::set ordering function:
The std::set is already ordered...
The std::set has its own ordering, and you are not supposed to change it once it is constructed. So, the following code:
int main(int argc, char* argv[])
{
std::set<std::string> aSet ;
aSet.insert("aaaaa") ;
aSet.insert("bbbbb") ;
aSet.insert("ccccccc") ;
aSet.insert("ddddddd") ;
aSet.insert("e") ;
aSet.insert("f") ;
outputSet(aSet) ;
return 0 ;
}
will output the following result:
- aaaaa
- bbbbb
- ccccccc
- ddddddd
- e
- f
... But you can customize its ordering function
Now, if you want, you can customize your set by using your own comparison function:
struct MyStringLengthCompare
{
bool operator () (const std::string & p_lhs, const std::string & p_rhs)
{
const size_t lhsLength = p_lhs.length() ;
const size_t rhsLength = p_rhs.length() ;
if(lhsLength == rhsLength)
{
return (p_lhs < p_rhs) ; // when two strings have the same
// length, defaults to the normal
// string comparison
}
return (lhsLength < rhsLength) ; // compares with the length
}
} ;
In this comparison functor, I did handle the case "same length but different content means different strings", because I believe (perhaps wrongly) that the behaviour in the original program is an error. To have the behaviour coded in the original program, please remove the if block from the code.
And now, you construct the set:
int main(int argc, char* argv[])
{
std::set<std::string, MyStringLengthCompare> aSet ;
aSet.insert("aaaaa") ;
aSet.insert("bbbbb") ;
aSet.insert("ccccccc") ;
aSet.insert("ddddddd") ;
aSet.insert("e") ;
aSet.insert("f") ;
outputSet(aSet) ;
return 0 ;
}
The set will now use the functor MyStringLengthCompare to order its items, and thus, this code will output:
- e
- f
- aaaaa
- bbbbb
- ccccccc
- ddddddd
But beware of the ordering mistake!
When you create your own ordering function, it must follow the following rule:
return true if (lhs < rhs) is true, return false otherwise
If for some reason your ordering function does not respect it, you'll have a broken set on your hands.
std::sort rearranges the elements of the sequence you give it. The arrangement of the sequence in the set is fixed, so the only iterator you can have is a const iterator.
You'll need to copy results into a vector or deque (or such) first.
vector sortable_results( results.begin(), results.end() );
You can customize the ordering of the elements in the set by providing a custom predicate to determine ordering of added elements relative to extant members. set is defined as
template <
class Key,
class Traits=less<Key>,
class Allocator=allocator<Key>
>
class set
where Traits is
The type that provides a function
object that can compare two element
values as sort keys to determine their
relative order in the set. This
argument is optional, and the binary
predicate less is the default
value.
There is background on how to use lambda expression as a template parameter here.
In your case this translates to:
auto comp = [](const string& a, const string& b) -> bool
{ return a.length() < b.length(); };
auto results = std::set <string, decltype(comp)> (comp);
Note that this will result in set elements with the same string length being treated as duplicates which is not what you want, as far as I can understand the desired outcome.
sort requires random access iterators which set doesn't provide (It is a bidirectional iterator). If you change the code to use vector it compiles fine.
You cannot sort a set. It's always ordered on keys (which are elements themselves).
To be more specific, std::sort requires random access iterators. The iterators provided by std::set are not random.
Since I wrote the original code you're using, perhaps I can expand on it... :)
struct cmp_by_length {
template<class T>
bool operator()(T const &a, T const &b) {
return a.length() < b.length() or (a.length() == b.length() and a < b);
}
};
This compares by length first, then by value. Modify the set definition:
set<string, cmp_by_length> results;
And you're good to go:
int main() {
using namespace std;
string s = "abc";
typedef set<string, cmp_by_length> Results; // convenience for below
Results results;
do {
for (int n = 1; n <= s.size(); ++n) {
results.insert(s.substr(0, n));
}
} while (next_permutation(s.begin(), s.end()));
// would need to add cmp_by_length below, if I hadn't changed to the typedef
// i.e. set<string, cmp_by_length>::const_iterator
// but, once you start using nested types on a template, a typedef is smart
for (Results::const_iterator x = results.begin(); x != results.end(); ++x) {
cout << *x << '\n';
}
// of course, I'd rather write... ;)
//for (auto const &x : results) {
// cout << x << '\n';
//}
return 0;
}
std::set is most useful to maintain a sorted and mutating list. It faster and smaller to use a vector when the set itself wont change much once it's been built.
#include <vector>
#include <string>
#include <iostream>
#include <algorithm>
int main() {
using namespace std;
string s = "abc";
vector<string> results;
do {
for (size_t n = 1; n <= s.size(); ++n) {
results.push_back(s.substr(0, n));
}
} while (next_permutation(s.begin(), s.end()));
//make it unique
sort( results.begin(), results.end() );
auto end_sorted = unique( results.begin(), results.end() );
results.erase( end_sorted, results.end() );
//sort by length
sort (results.begin(),results.end());
[](string lhs, string rhs)->bool
{ return lhs.length() < rhs.length(); } );
for ( const auto& result: results ) {
cout << result << '\n';
}
}
I used the classic, sort/unique/erase combo to make the results set unique.I also cleaned up your code to be a little bit more c++0x-y.