So I have a map with a key as a string, and I want to sort that map by the first value in the strings.
I want to sort the strings like this:
10 - 20
40 - 50
60 - 80
200 - 400
420+
I have a couple of more maps that I sort alphabetically. But this doesn't work for this case, because it would sort on the first letter, so it will become:
10 - 20
200 - 400
40 - 50
60 - 80
420+
Which I obviously do not want.
I normally sort like this:
dropdowncategory.KeySort([](const string& A, const string& B) {
return A.Compare(B, ESearchCase::IgnoreCase) < 0;
});
Simple.
But in this case from the A and B I want to get the first number, convert it to int and sort on that.
So what I did is I wrote a function (Not able to test it yet, but it should get the first number from the string, this is not the problem.):
int GetIntFromString(string s) {
int value = 0;
string L, R;
if (s.Contains("-")) {
s.Split("-", &L, &R);
}
else{
s.Split("+", &L, &R);
}
value = FCString::Atoi(*L);
return value;
}
From this function I will get the first number in the string. Then I want to sort on that outcome.
What I tried is the following:
dropdowncategory.KeySort([](const string& A,const string& B) {
return GetIntFromString(A) < GetIntFromString(B);
});
and:
string SA, SB;
dropdowncategory.KeySort([SA,SB](string& A, string& B) {
return GetIntFromString(SA) < GetIntFromString(SB);
});
But I keep getting and error on the GetIntFromString function in the return value of the KeySort:
Error (active) an enclosing-function local variable cannot be referenced in a lambda body unless it is in the capture list.
I am not sure why though.
I guess I cannot use that function inside the KeySort because it is a local function, but I don't know how to fix that.
As stated in std::stoi documentation:
Discards any whitespace characters (as identified by calling isspace()) until the first non-whitespace character is found, then takes as many characters as possible to form a valid base-n (where n=base) integer number representation and converts them to an integer value.
(emphasis is mine) it does what you need already, so your lambda can be just:
dropdowncategory.KeySort([](const string& A,const string& B) {
return std::stoi(A) < std::stoi(B);
});
note, converting string to integer is not very simple operation so you may want to reorganize your data so you do not have to do it for that strings so many times.
You can try to express your intent: treat the entries as pairs of ints, and use existing sorting logic for that:
std::vector<std::tuple<int, int>> pairs;
std::transform(begin(dropdowncategory), end(dropdowncategory),
std::back_inserter(pairs),
[](auto s){
std::stringstream ss(s);
int i; ss >> i;
char dummy; ss >> dummy;
int j; ss >> j;
return std::make_tuple(i, j);
});
std::sort(begin(pairs), end(pairs)); // 30, 50 will come before 200, 210
// convert pairs back to strings?
FString SA, SB;
dropdowncategory.KeySort([SA,SB](FString& A, FString& B) {
return GetIntFromString(SA) < GetIntFromString(SB);
});
Here you have 2 local string vars, empty (I suppose, no idea what's FString). You provide a lambda as a sorting function. Usually such function should have signature bool(const Key& left, const Key& right), and your lambda almost satisfied this requirement (pay attention to const). left and right here will be two keys to compare, and you need to define this comparison to define your sorting order. So correct version would be:
dropdowncategory.KeySort([](const FString& A, const FString& B) {
return GetIntFromString(A) < GetIntFromString(B);
});
This still doesn't explain your error, mainly because I have no idea what the error is.
By the way, GetIntFromString should be a free function, or at least a static member function, because its implementation depends only on input parameters.
P.S. listen to #Slava in the question comments, which can lead to
dropdowncategory.KeySort([](const FString& A, const FString& B) {
return std::atoi(*A) < std::atoi(*B);
});
Related
I need to check if two substrings are equal while inserting to a map. Here is the code:
class substring {
public:
substring(string* str, int offset, int length) : str(str), offset(offset), length(length) { }
bool operator < (const substring& val) const {
if (str->compare(offset, length, *val.str, val.offset, val.length) == 0) return false;
else return true;
}
int offset, length;
string* str;
};
This class above is a 'key' in my map. Lengths of both substrings are always same. Some of the conditions are wrong, cause it's still yelling 'invalid comparator'.
your if statement in comparation function code is convoluted way to say:
return str->compare(offset, length, *val.str, val.offset, val.length) != 0;
which is incorrect for comparison function that std::map requires. Remember you are implementing less than operator, not equivalence. If you want your substring to be sorted in ascending order use this:
return str->compare(offset, length, *val.str, val.offset, val.length) < 0;
I would recommend using const reference to std::string in you substring class - that will reflect the fact you do not accept nullptr as pointer and show intent that you do not want to change original string through this class and make your code cleaner.
I am trying to use std::string as a key in the stxxl::map
The insertion was fine for small number of strings about 10-100.
But while trying to insert large number of strings about 100000 in it, I am getting segmentation fault.
The code is as follows:
struct CompareGreaterString {
bool operator () (const std::string& a, const std::string& b) const {
return a > b;
}
static std::string max_value() {
return "";
}
};
// template parameter <KeyType, DataType, CompareType, RawNodeSize, RawLeafSize, PDAllocStrategy (optional)>
typedef stxxl::map<std::string, unsigned int, CompareGreaterString, DATA_NODE_BLOCK_SIZE, DATA_LEAF_BLOCK_SIZE> name_map;
name_map strMap((name_map::node_block_type::raw_size)*3, (name_map::leaf_block_type::raw_size)*3);
for (unsigned int i = 0; i < 1000000; i++) { /// Inserting 1 million strings
std::stringstream strStream;
strStream << (i);
Console::println("Inserting: " + strStream.str());
strMap[strStream.str()]=i;
}
In here I am unable to identify why I am unable to insert more number of strings. I am getting segmentation fault exactly while inserting "1377". Plus I am able to add any number of integers as key. I feel that the variable size of string might be causing this trouble.
Also I am unable to understand what to return for max_value of the string. I simply returned a blank string.
According to documentation:
CompareType must also provide a static max_value method, that returns a value of type KeyType that is larger than any key stored in map
Because empty string happens to compare as smaller than any other string, it breaks this precondition and may thus cause unspecified behaviour.
Here's a max_value that should work. MAX_KEY_LEN is just an integer which is larger or equal to the length of the longest possible string key that the map can have.
struct CompareGreaterString {
// ...
static std::string max_value() {
return std::string(MAX_KEY_LEN, std::numeric_limits<unsigned char>::max());
}
};
I have finally found the solution to my problem with great help from Timo bingmann, user2079303 and Martin Ba. Thank you.
I would like to share it with you.
Firstly stxxl supports POD only. That means it stores fixed sized structures only. Hence std::string cannot be a key. stxxl::map worked for about 100-1000 strings because they were contained in the physical memory itself. When more strings are inserted it has to write on disk which is internally causing some problems.
Hence we need to use a fixed string using char[] as follows:
static const int MAX_KEY_LEN = 16;
class FixedString {
public:
char charStr[MAX_KEY_LEN];
bool operator< (const FixedString& fixedString) const {
return std::lexicographical_compare(charStr, charStr+MAX_KEY_LEN,
fixedString.charStr, fixedString.charStr+MAX_KEY_LEN);
}
bool operator==(const FixedString& fixedString) const {
return std::equal(charStr, charStr+MAX_KEY_LEN, fixedString.charStr);
}
bool operator!=(const FixedString& fixedString) const {
return !std::equal(charStr, charStr+MAX_KEY_LEN, fixedString.charStr);
}
};
struct comp_type : public std::less<FixedString> {
static FixedString max_value()
{
FixedString s;
std::fill(s.charStr, s.charStr+MAX_KEY_LEN, 0x7f);
return s;
}
};
Please note that all the operators mainly((), ==, !=) need to be overriden for all the stxxl::map functions to work
Now we may define fixed_name_map for map as follows:
typedef stxxl::map<FixedString, unsigned int, comp_type, DATA_NODE_BLOCK_SIZE, DATA_LEAF_BLOCK_SIZE> fixed_name_map;
fixed_name_map myFixedMap((fixed_name_map::node_block_type::raw_size)*5, (fixed_name_map::leaf_block_type::raw_size)*5);
Now the program is compiling fine and is accepting about 10^8 strings without any problem.
also we can use myFixedMap like std::map itself. {for ex: myFixedMap[fixedString] = 10}
If you are using C++11, then as an alternative to the FixedString class you could use std::array<char, MAX_KEY_LEN>. It is an STL layer on top of an ordinary fixed-size C array, implementing comparisons and iterators as you are used to from std::string, but it's a POD type, so STXXL should support it.
Alternatively, you can use serialization_sort in TPIE. It can sort elements of type std::pair<std::string, unsigned int> just fine, so if all you need is to insert everything in bulk and then access it in bulk, this will be sufficient for your case (and probably faster depending on the exact case).
I'd like to sort a vector so that the capital letters follow the lower case letter. If I have something like
This is a test
this is a test
Cats
cats
this thing
I would like the output to be
cats
Cats
this is a test
This is a test
this thing
The standard library sort will output
Cats
This is a test
cats
this is a test
this thing
I want to pass a predicate to std::sort so that it compares the lowercase version of the strings that I pass as arguments.
bool compare(std::string x, std::string y)
{
return lowercase(x) < lowercase(y);
}
I tried lowering each character within the function and then making the comparison but it didn't work. I would like to test this approach by converting the string to lowercase by some other method. How do I convert strings into lowercase?
EDIT::
Actually I figured out the problem. This works. When I first wrote the function, instead of ref = tolower(ref) I had tolower(ref) without reassigning to ref so it wasn't doing anything.
bool compare(std::string x, std::string y)
{
for(auto &ref:x)
ref = tolower(ref);
for(auto &ref:y)
ref = tolower(ref);
return x < y;
}
EDIT::
This code actually sorts with the capital letter first sometimes and the capital letter second in other times so it doesn't solve the problem completely.
The usual way to do this would be to build a collation table. That's just a table giving the relative ordering of every character. In your case, you want each upper-case letter immediately following the corresponding lower-case letter.
We can do that something like this:
class comp_char {
std::vector<int> collation_table;
public:
comp_char() : collation_table(std::numeric_limits<unsigned char>::max()) {
std::iota(collation_table.begin(), collation_table.end(), 0);
for (int i = 0; i < 26; i++) {
collation_table['a' + i] = i * 2;
collation_table['A' + i] = i * 2 + 1;
}
}
bool operator()(unsigned char a, unsigned char b) {
return collation_table[a] < collation_table[b];
}
};
For the moment, I've ignored the (possibly knotty) problem of the relative ordering of letters to other characters. As it's written, everything else sorts before letters, but it would be pretty easy to change that so (for example) letters sorted before anything else instead. It probably doesn't make a huge difference either way though -- most people don't have strong expectations about whether 'a' < ';' or not.
In any case, once the collation table is built and usable, you want to use it to compare strings:
struct cmp_str {
bool operator()(std::string const &a, std::string const &b) {
comp_char cmp;
size_t i = 0;
while (a[i] == b[i] && i < a.size())
++i;
return cmp(a[i], b[i]);
}
};
...which we can use to do sorting, something like this:
int main(){
std::vector<std::string> inputs {
"This is a test",
"this is a test",
"Cats",
"cats",
"this thing"
};
std::sort(inputs.begin(), inputs.end(), cmp_str());
std::copy(inputs.begin(), inputs.end(),
std::ostream_iterator<std::string>(std::cout, "\n"));
}
For the moment, I've only written the collation table to handle the basic US-ASCII letters. For real use, you'd typically want to have things like letters with accents and such sort next to their corresponding un-accented equivalents. For that, you typically end up pre-building the table to (partially) match things like the Unicode specification for how things should be ordered.
Note that this output doesn't quite match what the original question says is desired, but I think in this case the question has a mistake. I can't see any way it would be even marginally reasonable to produce an order like:
this is a test
This is a test
this thing
This has "T" sorting both after and before "t", which doesn't seem to make sense (or at least doesn't fit with a lexical sort, which is what people nearly always want for strings).
The simplest solution is to use the collation-aware sorting provided by the standard locale object.
A locale's operator()(std::string, std::string) is exactly the locale's collation-aware comparison operator, so you can just insert it directly into your call to std::sort:
// Adjust to the locale you actually want to use
std::sort(strings.begin(), strings.end(), std::locale("en_US.UTF-8"));
Example on ideone
Your solution is almost there, you just need to make a special case if the lower case version of the strings are equal:
std::string to_lower(std::string s)
{
for (auto & c : s)
c = std::tolower(c);
return s;
}
bool string_comp(std::string const & lhs, std::string const & rhs)
{
auto lhs_lower = to_lower(lhs);
auto rhs_lower = to_lower(rhs);
if (lhs_lower == rhs_lower)
return rhs < lhs;
return lhs_lower < rhs_lower;
}
This could use some optimization. Copying the string is not necessary. You can, of course, do a case insensitive comparison in place. But that is feature is not conveniently available in the standard library, so I'll leave that exercise up to you.
To be clear, I was aiming at the usual lexicographic type comparison but somehow make uppercase follow the lowercase if the strings were identical otherwise.
This requires a two-steps comparison then:
compare the strings in case-insensitive mode
if two strings are equal in case-insensitive mode, we want the reverse result of a case sensitive comparison (which puts upper-case first)
So, the comparator gives:
class Comparator {
public:
bool operator()(std::string const& left, std::string const& right) {
size_t const size = std::min(left.size(), right.size());
// case-insensitive comparison
for (size_t i = 0; i != size; ++i) {
if (std::tolower(left[i]) < std::tolower(right[i])) { return true; }
}
if (left.size() != right.size()) { return size == left.size(); }
// and now, case-sensitive (reversed)
return right < left;
}
}; // class Comparator
You need to do the comparison one char at a time, stopping at the first different char and then returning the result depending on the case conversion first, and on original char otherwise:
bool mylt(const std::string& a, const std::string& b) {
int i=0, na=a.size(), nb=b.size();
while (i<na && i<nb && a[i]==b[i]) i++;
if (i==na || i==nb) return i<nb;
char la=std::tolower(a[i]), lb=std::tolower(b[i]);
return la<lb || (la==lb && a[i]<b[i]);
}
Warning: untested breakfast code
Either use locals that already have the ordering you want, or write a character by character comparison function then use std::lexicographical_compare to turn it into a string comparison function.
I would try locals first, but if that proved frustrating the lexicographic is not horrible.
To compare chqracters, create two tuples or pairs of lower_case_letter, unchanged_letter, and call < on it. This will first order by lower case, then if that fails by the unchanged. I forget what order the upper vs lower will sort in: but if the order is backwards, just swap which lower case letter gets paired with which upper case letter, and you'll reverse the order!
I was reading following question:
How to sum up elements of a C++ vector?, and I wanted to use second method (sum_of_elems =std::accumulate(vector.begin(),vector.end(),0);//#include <numeric>).
However, I don't have std::vector<int>, but std::vector<struct packet>. The packet is defined as following:
struct packet {
/// ...
int length() const;
///
}
and I want sum of packet lengths.
This is what I tried:
std::vector<packet> packets;
...
std::accumulate(packets.begin(), packets.end(), 0, [](const packet& a, const packet& b){return a.length() + b.length();});
but it doesn't work. In C#, I'd write something like
packet[] p;
p.Select(x => p.length()).Sum();
Is it possible to do something like that in C++? I can write method for iterating through the vector and doing it on my own, but I prefer the functional approach where possible.
I would note that the C# implementation is slightly different, in essence.
In C++ you are trying to add int and packet whilst in C# you first provide a transformation step from packet to int and then add ints.
The equivalent C++, without adaptation:
std::vector<size_t> lengths; // a length cannot be negative!
std::transform(packets.begin(),
packets.end(),
backward_inserter(lengths),
[](packet const& p) { return p.length(); });
auto const sum = std::accumulate(lengths.begin(), lengths.end(), 0ul);
Of course, it is wasteful to store the intermediate lengths, however it does work out of the box.
But because we are cool, let us have look at Boost.Range, and more precisely:
boost::accumulate
boost::transformed
Which have a bit of coolness like Linq:
#include <boost/range/numeric.hpp> // accumulate
#include <boost/range/adaptor/transformed.hpp>
size_t total_size(std::vector<packet> const& packets) {
return boost::accumulate(
packets | boost::transformed([](packet const& p) { return p.length(); }),
0ul);
}
You are accumulating via a binary operation. Your accumulated value starts with 0 (an int), so the left hand side of your binary operation has to be convertible-from 0 -- otherwise, how does it start adding?
Try this:
std::accumulate(
packets.begin(),
packets.end(),
0,
[](int a, const packet& b){
return a + b.length();
}
);
you can also do this via a simple loop:
int acc = 0;
for( const packet& p : packets ) {
acc += packets.length();
}
The first parameter of the accumulate operation is the running total. In your case, this is an integer, not a packet, so your lambda should be
[](int a, const packet& b) {return a + b.length();}
The problem is your accumulate function. Its first parameter has to be of the type you're trying to accumulate (int in this case) and add a value on top of that.
Your lambda function should look like this: [](int currTotal, const packet& b){return currTotal + b.length();}
Apart from lamba, you can change it to
std::accumulate(packets.begin(), packets.end(), 0, packet());
Where you can define functor as:
int operator() (int result, const packet& obj)
{
return result+ obj.length();
}
C++ Sorting Array Class
I have an array object that record the following..
This is at classone.h
ClassOne
{
string name;
int data;
float valueData;
}
and the constructor are created at classone.cpp
At main.cpp I created ClassOne Array of Size 10
#include "classone.h"
ClassOne cone[10];
Next is i recorded several value to the object
and now ClassOne got 3 objects
cone[0]
name = "hello"
data = 1
valueData = 20
cone[1]
name = "panda"
data = 2
valueData = 15
cone[2]
name = "joe"
data = 3
valueData = 25
What i want to achieve is do a sort that can rearrange this array by valueData highest ascending form so.. it will be
cone[2] then cone[0] then cone[1] ..
but the issue if i use bubble sort , i tried google and find some, they are sorting by e.g int a[]={9,6,5,23,2,6,2,7,1,8};
but i wanna sort by class array object. and re-arrange the value together , how do i achieve this.
So when i cout it will be
-- Highest to lowest --
1) Name: Joe , Data = 3, Value =25
2) Name: Hello , Data =1 , Value = 20
3) Name: Panda, Data = 2, Value = 15
Thanks for all help and guide!!
The easiest way is to use the standard library:
#include <algorithm>
std::sort(cone, cone + 10,
[](ClassOne const & a, ClassOne const & b) -> bool
{ return a.value < b.value; } );
If you're willing to define a comparison operator globally, you don't even need the lambda:
bool operator<(ClassOne const & a, ClassOne const & b)
{
return a.value < b.value;
}
std::sort(cone, cone + 10);
Or you could make the comparator a member function. Or you could give the comparator function a custom name and pass that as the third argument of sort. This might be a good idea in the case where the comparison is specific to your situation and not "natural":
bool ValueCmp(ClassOne const & a, ClassOne const & b)
{
return a.value < b.value;
}
std::sort(cone, cone + 10, ValueCmp);
The last version is useful if you don't have C++11 support (for lambdas, as in the first case), or if you want to reuse the comparator in multiple different situations.
Use std::sort and a suitable sort function/functor:
bool comp(const ClassOne& lhs, const ClassOne& rhs)
{
return lhs.valueData < rhs.valueData;
}
std::sort(cone, cone+10, comp);
or, in C++11,
std::sort(std::begin(cone), std::end(cone), comp);
You can make a struct that implements the operator < method that std::sort in the <algorithm> header uses to sort iterated items.
struct One {
string name;
int data;
float valueData;
bool operator < (const one &a) const{
return valueData <a.valueData;
}
};
then all you have to do is to make an array of this struct and sort it using the sort function
Look at your Bubble sort source. At some point, it will be comparing one int to another, probably with either the less than operator (<) or the greater than operator (>). That's where the sort function determines the relative order of those two items. By repeating that comparison many times, the sort function is able to determine the total order of the collection.
You need to replace that operation with your own comparison function. A function that takes two objects of your class, and returns true if the first should be considered less than the second, false if the second should be considered less than the first, and false if they should be considered equivalent.
You must define a comparison operator for your class. How you determine whether one object is less than another isn't clear from your question.
Try this
...
....
void ClassOne::sort(ClassOne *obj,int n)
{
ClassOne temp;
int i, j;
for (i = 0; i < n; i++)
for (j = n - 1; j > i; j--)
if (obj[j].valueData <obj[j - 1].valueData )
{
temp = obj[j];
obj[j] = obj[j - 1];
obj[j - 1] = temp;
}
}
...
int main()
{
ClassOne obj[3],a;
for(int i=0;i<3;i++)
obj[i].readdata();
a.sort(obj,3);
...
}