merge std::set on one member variable - c++

My requirement here is to merge the qty of both the sets if the price is same but isImplied bool are different.
Current Output:
Price : 100
IsImplied : 0
Qty :10
Price : 200
IsImplied : 0
Qty : 20
As the price 100 and 200 were already present in the set the insertion of p3 and p4 is ignored.
Desired output:
Price : 100
IsImplied : 0
Qty :40 (10 + 30) (Qty is merged as P1 and P3 have same price but different isImplied values)
Price : 200
IsImplied : 0
Qty : 60 (20 + 40) (Qty is merged as P2 and P4 have same price but different isImplied values)
class PriceLevel
{
public:
int price;
int qty;
bool isImplied;
PriceLevel(int _price, int _qty, bool _isImplied)
{
price = _price;
qty = _qty;
isImplied = _isImplied;
}
friend bool operator<(const PriceLevel &p, const PriceLevel &q);
};
bool operator<(const PriceLevel &p, const PriceLevel &q)
{
if(p.price < q.price)
{
return true;
}
else
{
return false;
}
}
int main()
{
std::set<PriceLevel> s1;
PriceLevel p1(100,10, false);
PriceLevel p2(200,20, false);
PriceLevel p3(100,30, true);
PriceLevel p4(200,40, true);
s1.insert(p1);
s1.insert(p2);
s1.insert(p3);
s1.insert(p4);
set<PriceLevel>::iterator it = s1.begin();
for(; it != s1.end(); it++)
{
cout << "Price: " << it->price << endl;
cout << "Qty : " << it->qty << endl;
cout << "IsImplied: " << it->isImplied << endl;
}
}

If you need to retain the quantity as well, your compare function should use that information. set comparison works on strict weak ordering.
There are two ways to achieve this. Pick the one that fits your design best.
1.Instead of keeping a set of PriceLevel itself, keep a map with the key as the Price and value as the quantity. Your update function will look something like:
void update(map<int, int> &valueMap, const PriceList &val)
{
valueMap[val.price] += val.qty;
}
`
2. Modify the insertion logic in your set. Update would look something like:
void update(set<PriceList> &valueMap, PriceList val)
{
auto iter = valueMap.find(val);
if (iter != valueMap.end())
{
val.qty = iter->qty + val.qty;
valueMap.erase(iter);
}
valueMap.insert(val);
}
and obviously your compare function needs to be updated to account for qty.
It should look something like
bool comp(const PriceList& val1, const PriceList& val2)
{
return make_pair(val1.price, val1.qty) < make_pair(val2.price, val2.qty);
}

You want to do something like like the following. Note we only do a single lookup.
// attempt to insert
std::pair<bool, std::set<PriceLevel>::iterator> result = s1.insert(p1);
if (result.first) // insert did not work since element already existed
{
PriceLevel & temp = *(result.second);
if (temp.isImplied != p1.isImplied)
{
temp.qty += p1.qty; // sum
}
else
{
temp.qty = p1.qty; // overwrite
}
}
// otherwise p1 didn't exist and was inserted

Related

std::map<struct,struct>::find is not finding a match, but if i loop thru begin() to end() i see the match right there

struct chainout {
LONG cl;
std::string cs;
bool operator<(const chainout&o)const {
return cl < o.cl || cs < o.cs;
}
} ;
struct chainin{
std::string tm;
std::string tdi;
short mss;
LONG pinid;
bool operator<(const chainin&o)const {
return mss < o.mss || pinid < o.pinid || tm<o.tm; //no tdi right now it's always empty
}
};
std::map <chainin,chainout> chainmap;
std::map<chainin,chainout>::iterator it;
chainin ci;
chainout co;
string FADEDevicePinInfo::getNetAtPinIdTmTidMss (const LONG p,const string tm, const string tid,const LONG mss){
ci.tm=tm;
// ci.tdi=tid;
ci.tdi="";
ci.mss=(short)mss;
ci.pinid=p;
for (it=chainmap.begin();it!=chainmap.end();it++){
if(it->first.pinid==ci.pinid && it->first.tm==ci.tm&&it->first.mss==ci.mss && it->first.tdi==ci.tdi){
cout << "BDEBUG: found p["; cout<<it->first.pinid; cout<<"] tm["; cout<<it->first.tm.c_str();cout<<"] mss[";cout<<it->first.mss;cout<<"] : ";cout<<it->second.chainSignal.c_str();cout<<endl;
}
}
it=chainmap.find(ci);
if(it == chainmap.end()){
MSG(SEV_T,("no pin data found for pin[%ld]/tm[%s]/tdi[%s]/mss[%ld]",ci.pinid,ci.tm.c_str(),ci.tdi.c_str(),ci.mss));
}
return it->second.cs;
}
This is both printing the successfully found line, and then throwing the sev_t error due to map::find not returning a match. what did i do wrong?
I added print statements thruout the < function, but it seems to be ordering the map correctly, and when i do the lookup, it seems to find the correct mss/pinid, but then only sees one tm, which is the wrong tm.
As noted in comments, you have a bad comparison operator. If you don't know what order the objects should be sorted in, then neither does std::map or any other sorted container.
When you have multiple things to compare, consider deciding which is most important, and use std::tie to compare them, as demonstrated here:
#include <string>
#include <iostream>
struct chainout {
int cl;
std::string cs;
bool operator<(const chainout&o)const {
return std::tie(cl, cs) < std::tie(o.cl, o.cs);
}
};
int main(){
chainout a{ 1, "b" };
chainout b{ 2, "a" };
std::cout << (a < b) << std::endl;
std::cout << (b < a) << std::endl;
}
The operator< for both of your structs are implemented incorrectly.
std::map requires key comparisons to use Strict Weak Ordering. That means when your structs want to compare multiple fields, they need to compare later fields only when earlier fields compare equal. But you are not checking for that condition. You are returning true if any field in one instance compares less-than the corresponding field in the other instance, regardless of the equality (or lack of) in the other fields. So you are breaking SWO, which causes undefined behavior in std::map's lookups.
Try this instead:
struct chainout {
LONG cl;
std::string cs;
bool operator<(const chainout &o) const {
/*
if (cl < o.cl) return true;
if (cl == o.cl) return (cs < o.cs);
return false;
*/
return (cl < o.cl) || ((cl == o.cl) && (cs < o.cs));
}
};
struct chainin{
std::string tm;
std::string tdi;
short mss;
LONG pinid;
bool operator<(const chainin &o) const {
if (mss < o.mss) return true;
if (mss == o.mss) {
if (pinid < o.pinid) return true;
if (pinid == o.pinid) return (tm < o.tm);
}
return false;
}
};
An easier way to implement this is to use std::tie() instead, which has its own operator< to handle this for you, eg:
struct chainout {
LONG cl;
std::string cs;
bool operator<(const chainout &o) const {
return std::tie(cl, cs) < std::tie(o.cl, o.cs);
}
};
struct chainin{
std::string tm;
std::string tdi;
short mss;
LONG pinid;
bool operator<(const chainin &o) const {
return std::tie(mss, pinid, tm) < std::tie(o.mss, o.pinid, o.tm);
}
};
Either way, then std::map::find() should work as expected, eg:
std::map<chainin, chainout> chainmap;
string FADEDevicePinInfo::getNetAtPinIdTmTidMss (const LONG p, const string tm, const string tid, const LONG mss)
{
chainin ci;
ci.tm = tm;
//ci.tdi = tid;
ci.tdi = "";
ci.mss = (short) mss;
ci.pinid = p;
std::map<chainin, chainout>::iterator it = chainmap.find(ci);
if (it != chainmap.end()) {
cout << "BDEBUG: found"
<< " p[" << it->first.pinid << "]"
<< " tm[" << it->first.tm << "]"
<< " mss[" << it->first.mss << "]"
<< " : " << it->second.cs
<< endl;
}
}

problem with using struct as std::map key

my code is like this:
struct Info
{
string name;
int score;
bool operator< (const Info &x) const
{
return score < x.score;
}
};
int main(int argc, char *argv[])
{
Info a, b;
a.name = "eric";
a.score = 90;
b.name = "cat";
b.score = 85;
map<Info, int> m;
m[a] = 1;
m[b] = 2;
map<Info, int>::iterator it;
for(it = m.begin(); it != m.end(); it++)
{
cout << it->first.name << endl;
}
return 0;
}
it prints out "cat" and "eric", as expected. but how ever, when I modify it to (make a.score and b.score the same)
Info a, b;
a.name = "eric";
a.score = 90;
b.name = "cat";
b.score = 90;
It prints out "eric" only, there's only one element in the whole map.
question: does std::map think they are the same key? how do I make std::map think they are not the same key? I tried operator==, but not working.
They are the same key because your custom comparator uses the score as the key. Try this instead
bool operator< (const Info &x) const
{
return name < x.name;
}
If you want the name to be the key but the map to be sorted on the score, then I'm afraid you are out of luck, because maps are sorted on the key by definition. You'll have to pick another data structure or another algorithm.

C++ List showing last 3 items from table

Hey i have a table of teams with the names and the points they have and i'm trying to figure out how to display the last 3 teams with the least amount of points in the table?
It displays all the teams and i want it to display only the last 3 in the table but don't know what way to go about it.
These are my Accessors
string GetName
int GetPoints
int lowest = 1000;
for (int i = 0; i < numTeams; i++)
{
if (league[i].GetPoints() < lowest)
{
lowest = league[i].GetPoints();
}
}
for (int i = 0; i < numTeams; i++)
{
if (league[i].GetPoints() == lowest)
{
cout << "\tThe lowest goals against is: " << league[i].GetName() << endl;
}
}
Actually, you don't need variable lowest, if you would sort the data before printing.
#include <algorithm>
// Sort using a Lambda expression.
std::sort(std::begin(league), std::end(league), [](const League &a, const League &b) {
return a.GetPoints() < b.GetPoints();
});
int last = 3;
for (int i = 0; i < last; i++)
{
cout << "\tThe lowest goals against is: " << league[i].GetName() << endl;
}
U could probably start by sorting your array
#include <algorithm>
std::array<int> foo;
std::sort(foo.begin(), foo.end());
and then Iterate From Your Last Element to your Last - 3. (U can use Reverse Iterators)
for (std::vector<int>::reverse_iterator it = v.rend() ; it != v.rend() + 3;
it++) {
//Do something
}
or by using auto
for (auto it = v.rend() ; it != v.rend() + 3; ++it) {
//Do something
}
In my example I've created test class(TestTeam) to implement several important methods for objects in your task.
I use std::sort method to sort container of objects, by default std::sort compares objects by less(<) operation, so I have overrided operator < for TestTeam object
bool operator < ( const TestTeam& r) const
{
return GetPoints() < r.GetPoints();
}
Also we could pass as third parameter another compare method or lambda method as shown in below answers:
std::sort(VecTeam.begin(), VecTeam.end(), [](const TestTeam& l, const TestTeam& r)
{
return l.GetPoints() < r.GetPoints();
});
And example when we use global method to compare:
bool CompareTestTeamLess(const TestTeam& l, const TestTeam& r)
{
return l.GetPoints() < r.GetPoints();
}
//...
// some code
//...
// In main() we use global method to sort
std::sort(VecTeam.begin(), VecTeam.end(), ::CompareTestTeamLess);
You can try my code with vector as container:
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
// Test class for example
class TestTeam
{
public:
TestTeam(int16_t p, const std::string& name = "Empty name"):mPoints(p), mName(name)
{
};
int16_t GetPoints() const {return mPoints;}
const std::string& GetName() const {return mName;}
void SetName( const std::string& name ) {mName=name;}
bool operator < ( const TestTeam& r) const
{
return GetPoints() < r.GetPoints();
}
private:
int16_t mPoints;
std::string mName;
};
int main(int argc, const char * argv[])
{
const uint32_t COUNT_LOWEST_ELEMENTS_TO_FIND = 3;
// Fill container by test data with a help of non-explicit constructor to solve your task
std::vector<TestTeam> VecTeam {3,5,8,9,11,2,14,7};
// Here you can do others manipulations with team data ...
//Sort vector by GetPoints overloaded in less operator. After sort first three elements will be with lowest points in container
std::sort(VecTeam.begin(), VecTeam.end());
//Print results as points - name
std::for_each( VecTeam.begin(), VecTeam.begin() + COUNT_LOWEST_ELEMENTS_TO_FIND, [] (TestTeam el)
{
std::cout << el.GetPoints() << " - " << el.GetName() << std::endl;
} );
}
I made test class TestTeam only to implement test logic for your object.
If you try launch the program you can get next results:
2 - Empty name
3 - Empty name
5 - Empty name
Program ended with exit code: 0

unordered_map for custom class does not cause error when inserting the same key

I'm trying to figure out some points on using unordered_map for custom class. Below are the codes I use to take exercise where I define a simple class Line. I'm confused that why the insertion of Line2 in main() does not make the program outputs insert failed when the value of m for Line1 and Line2 are both 3. Note since I only compare the first value (i.e. m) in the operator== function in class Line, thus Line1 and Line2 in this code should have the same key. Shouldn't the insertion of an already existed key be invalid? Could some one explain why to me? Thanks!
#include<iostream>
#include<unordered_map>
using namespace std;
class Line {
public:
float m;
float c;
Line() {m = 0; c = 0;}
Line(float mInput, float cInput) {m = mInput; c = cInput;}
float getM() const {return m;}
float getC() const {return c;}
void setM(float mInput) {m = mInput;}
void setC(float cInput) {c = cInput;}
bool operator==(const Line &anotherLine) const
{
return (m == anotherLine.m);
}
};
namespace std
{
template <>
struct hash<Line>
{
size_t operator()(const Line& k) const
{
// Compute individual hash values for two data members and combine them using XOR and bit shifting
return ((hash<float>()(k.getM()) ^ (hash<float>()(k.getC()) << 1)) >> 1);
}
};
}
int main()
{
unordered_map<Line, int> t;
Line line1 = Line(3.0,4.0);
Line line2 = Line(3.0,5.0);
t.insert({line1, 1});
auto x = t.insert({line2, 2});
if (x.second == false)
cout << "insert failed" << endl;
for(unordered_map<Line, int>::const_iterator it = t.begin(); it != t.end(); it++)
{
Line t = it->first;
cout << t.m << " " << t.c << "\n" ;
}
return 1;
}
Your hash and operator == must satisfy a consistency requirement that they currently violate. When two objects are equal according to ==, their hash codes must be equal according to hash. In other words, while non-equal objects may have the same hash code, equal objects must have the same hash code:
size_t operator()(const Line& k) const {
return hash<float>()(k.getM());
}
Since you compare only one component for equality, and ignore the other component, you need to change your hash function to use the same component that you use to decide the equality.
you are using both the values of "m" and "c" in you hash, so 2 "Line" instances would have the same key if both their "m" and "c" are equal, which is not the case in your example. So if you do this :
Line line1 = Line(3.0,4.0);
Line line2 = Line(3.0,4.0);
t.insert({line1, 1});
auto x = t.insert({line2, 2});
if (x.second == false)
cout << "insert failed" << endl;
you'll see that it will print "insert failed"
You can always use custom function to compare the keys upon insertion:
#include <iostream>
#include <unordered_map>
class Line {
private:
float m;
float c;
public:
Line() { m = 0; c = 0; }
Line(float mInput, float cInput) { m = mInput; c = cInput; }
float getM() const { return m; }
float getC() const { return c; }
};
struct hash
{
size_t operator()(const Line& k) const
{
return ((std::hash<float>()(k.getM()) ^ (std::hash<float>()(k.getC()) << 1)) >> 1);
}
};
// custom key comparison
struct cmpKey
{
bool operator() (Line const &l1, Line const &l2) const
{
return l1.getM() == l2.getM();
}
};
int main()
{
std::unordered_map<Line, int, hash, cmpKey> mymap; // with custom key comparisom
Line line1 = Line(3.0, 4.0);
Line line2 = Line(4.0, 5.0);
Line line3 = Line(4.0, 4.0);
auto x = mymap.insert({ line1, 1 });
std::cout << std::boolalpha << "element inserted: " << x.second << std::endl;
x = mymap.insert({ line2, 2 });
std::cout << std::boolalpha << "element inserted: " << x.second << std::endl;
x = mymap.insert({ line3, 3 });
std::cout << std::boolalpha << "element inserted: " << x.second << std::endl;
return 0;
}
Prints:
element inserted: true
element inserted: true
element inserted: false

Merge two std::sets

I need to merge two sets into a resultant set on basis of one member variable qty if the prices are same. In the below example my resultant set s3 should contain:
Price : 100
Qty : 40
Price : 200
Qty : 60
Please note qty above is a sum of qty in both the sets respective when the price is same.
My question is how do I construct the set s3 below:
Please guide me with the same.
#include <set>
#include <iostream>
using namespace std;
class PriceLevel
{
public:
int price;
int qty;
PriceLevel(int _price, int _qty)
{
price = _price;
qty = _qty;
}
friend bool operator<(const PriceLevel &p, const PriceLevel &q);
};
bool operator<(const PriceLevel &p, const PriceLevel &q)
{
if(p.price < q.price)
{
return true;
}
else
{
return false;
}
}
int main()
{
std::set<PriceLevel> s1;
std::set<PriceLevel> s2;
PriceLevel p1(100,10);
PriceLevel p2(200,20);
PriceLevel p3(100,30);
PriceLevel p4(200,40);
s1.insert(p1);
s1.insert(p2);
s2.insert(p3);
s2.insert(p4);
std::set<PriceLevel> s3;
set<PriceLevel>::iterator it = s3.begin();
// How should I Initialize s3
for(; it != s3.end(); it++)
{
cout << "Price: " << it->price << endl;
cout << "Qty : " << it->qty << endl;
}
}
If you are absolutely sure that both source sets contain exactly the same prices, you can use the binary version of std::transform.
If they might contain unequal data, you'll have to do it manually, like this:
std::set<PriceLevel> s3;
// How should I Initialize s3
std::set<PriceLevel>::iterator
first1 = s1.begin(),
last1 = s1.end(),
first2 = s2.begin(),
last2 = s2.end();
while (first1 != last1 && first2 != last2) {
if (first1->price < first2->price) {
s3.insert(*first1++);
}
else if (first1->price > first2->price) {
s3.insert(*first2++);
}
else {
s3.insert(PriceLevel(first1->price, first1->qty + first2->qty));
++first1;
++first2;
}
}
while (first1 != last1) {
s3.insert(*first1++);
}
while (first2 != last2) {
s3.insert(*first2++);
}
This is best put in an extra function.
View on IdeOne
If you only need those prices in the result set which existed in both source sets, it is a bit simpler:
while (first1 != last1 && first2 != last2) {
if (first1->price < first2->price) {
++first1;
}
else if (first1->price > first2->price) {
++first2;
}
else {
s3.insert(PriceLevel(first1->price, first1->qty + first2->qty));
++first1;
++first2;
}
}
You can merge two sets with just two lines
#include <set>
template <typename _Ty>
std::set<_Ty> merge(const std::set<_Ty> &x, const std::set<_Ty> &y) const
{
std::set<_Ty> merged = x; //initial merged set from x
merged.insert(y.begin(), y.end()); //add contents of y to merged
return move(merged);
}
set is not an appropriate data structure for your application here. Consider using a map<int, int> instead:
map<int, int> p1, p2, p3; // map price -> quantity
p1[100] = 10;
p1[200] = 20;
p2[100] = 30;
p2[200] = 40;
p3 = p1;
for(auto &i : p2) {
p3[i.first] += i.second;
}
// Now p3[100]=40 and p3[200]=60.
You can also use a set kind of like a map using set::find:
s3 = s1;
for(auto &i : s2) {
auto it = s3.find(i);
if(it == s3.end()) {
s3.insert(i);
} else {
it->qty += i.qty;
}
}
For this to work, you will have to declare qty as a mutable int, so that it can be modified even if the PriceLevel struct is const (since elements of a set are const).
If you can't make the variable mutable, then you can try removing the existing set element and then adding a new, merged element.
You are essentially trying to use a set as a map AND merge values with equal keys. You will need to roll your own result (not to mention that it really isn't advisable...). Here is something to get you started.
#include <iostream>
#include <set>
using namespace std;
class PriceLevel
{
public:
int price;
int qty;
PriceLevel() {
price = 0;
qty = 0;
}
PriceLevel(int _price, int _qty)
{
price = _price;
qty = _qty;
}
friend bool operator<(const PriceLevel &p, const PriceLevel &q);
//Compares two PriceLevel objects and merges their values if their keys are the same.
//Return value is a std::pair that
//denotes if the compare was successful and the result is meaningful.
static std::pair<bool, PriceLevel> merge_equal(const PriceLevel& p, const PriceLevel& q) {
std::pair<bool, PriceLevel> result;
result.first = false;
if(p.price == q.price) {
result.first = true;
result.second.price = p.price;
result.second.qty = p.qty + q.qty;
}
return result;
}
};
bool operator<(const PriceLevel &p, const PriceLevel &q)
{
if(p.price < q.price)
{
return true;
}
else
{
return false;
}
}
int main()
{
std::set<PriceLevel> s1;
std::set<PriceLevel> s2;
PriceLevel p1(100,10);
PriceLevel p2(200,20);
PriceLevel p3(100,30);
PriceLevel p4(200,40);
s1.insert(p1);
s1.insert(p2);
s2.insert(p3);
s2.insert(p4);
std::set<PriceLevel> s3;
//Just in case...the world may explode otherwise.
if(s1.size() == s2.size()) {
for(const auto& pl1 : s1) {
for(const auto& pl2 : s2) {
//Only insert valid values.
auto r = PriceLevel::merge_equal(pl1, pl2);
if(r.first) s3.insert(r.second);
}
}
for(auto it = s3.begin(); it != s3.end(); it++) {
cout << "Price: " << it->price << endl;
cout << "Qty : " << it->qty << endl;
}
}
}