C++ - Find adjacent elements in std::map - c++

What is the most efficient way to look up the adjacent elements in a STL map using the examples I mention below:
Suppose I have a map of integer - string:
1 -> Test1
5 -> Test2
10 -> Test3
20 -> Test4
50 -> Test5
If I call:
get_adjacent(1) // Returns iterator to 1 and 5
get_adjacent(2) // Returns iterator to 1 and 5
get_adjacent(24) // Returns iterator to 20 and 50
get_adjacent(50) // Returns iterator to 20 and 50

Use std::lower_bound and std::upper_bound for exactly this.
Better yet std::map::equal_range combines the power of both:
See it live on http://liveworkspace.org/code/d3a5eb4ec726ae3b5236b497d81dcf27
#include <map>
#include <iostream>
const auto data = std::map<int, std::string> {
{ 1 , "Test1" },
{ 5 , "Test2" },
{ 10 , "Test3" },
{ 20 , "Test4" },
{ 50 , "Test5" },
};
template <typename Map, typename It>
void debug_print(Map const& map, It it)
{
if (it != map.end())
std::cout << it->first;
else
std::cout << "[end]";
}
void test(int key)
{
auto bounds = data.equal_range(key);
std::cout << key << ": " ; debug_print(data, bounds.first) ;
std::cout << ", " ; debug_print(data, bounds.second) ;
std::cout << '\n' ;
}
int main(int argc, const char *argv[])
{
test(1);
test(2);
test(24);
test(50);
}
Outputs:
1: 1, 5
2: 5, 5
24: 50, 50
50: 50, [end]

Related

Counting Duplicates in C++ - multiset?

UPD:-
Value Instances
 2   3
 3   2
 5   1
I want to limit the count to 1 for all the instances present in the multiset.
#include<bits/stdc++.h>
using namespace std;
int main() {
multiset<int> p1;
p1.insert(5);
p1.insert(2);
p1.insert(3);
p1.insert(3);
p1.insert(2);
p1.insert(2);
for(auto itr : p1) {
if(p1.count(itr) > 1)
p1.erase(itr);
cout << itr;
}
}
How to fix this ?
My comment:
In that case, you should use a std::set<int> because that is actually what matches your requirement. You could use also a std::map<int, int> to map the key to the number of occurrences if you like.
OPs reply:
Can you add this to a full-fledged answer so that I can accept it for this question?
Here we go:
Just filtering duplicates:
#include <iostream>
#include <set>
int main()
{
int sample[] = { 5, 2, 3, 3, 2, 2 };
// add all values at most once
using Table = std::set<int>;
Table table;
for (int value : sample) table.insert(value);
// output the result
for (const Table::value_type& entry : table) {
std::cout << "Value " << entry << "\n";
}
}
Output:
Value 2
Value 3
Value 5
Demo on coliru
Counting the number of occurrences:
#include <iostream>
#include <map>
int main()
{
int sample[] = { 5, 2, 3, 3, 2, 2 };
// add all values at most once but count the number of occurrences
using Table = std::map<int, unsigned>;
Table table;
for (int value : sample) ++table[value];
// output the result
for (const Table::value_type& entry : table) {
std::cout << "Value " << entry.first << " (" << entry.second << " times)\n";
}
}
Output:
Value 2 (3 times)
Value 3 (2 times)
Value 5 (1 times)
Demo on coliru
The trick:
The std::map::operator[] inserts an element if the key is not yet there. This element (in this case std::pair<const int, unsigned>) is default initialized which grants that it starts as { key, 0 }.
So, there are two cases:
The key is not yet there:
The element is created as { key, 0 } and the value (.second of the element) is incremented immediately which results in { key, 1 }.
The key is already there:
The value (.second of the element) is incremented again.
A variation on filtering duplicates:
This keeps the original input order but removes repetitions (by book-keeping in a separate std::set).
#include <iostream>
#include <set>
#include <vector>
int main()
{
using Sample = std::vector<int>;
Sample sample = { 5, 2, 3, 3, 2, 2 };
// remove duplicates
using Table = std::set<int>;
Table table;
Sample::iterator iterRead = sample.begin();
Sample::iterator iterWrite = sample.begin();
for (; iterRead != sample.end(); ++iterRead) {
if (table.insert(*iterRead).second) *iterWrite++ = *iterRead;
}
sample.erase(iterWrite, sample.end());
// output the result
for (const Sample::value_type& entry : sample) {
std::cout << "Value " << entry << "\n";
}
}
Output:
Value 5
Value 2
Value 3
Demo on coliru
The trick:
std::set::insert() returns a pair of iterator and bool.
The iterator points to the key in the set (inserted or already been there).
The bool denotes if the key was inserted (true) or was already there (false).
The other trick:
Just erasing every found duplicate from the std::vector would result in the worse complexity O(n²).
Hence, two iterators are used, one for reading and one for writing. Thereby, every input value which is not yet in the bookkeeping table (and hence occurs the first time) is written back, otherwise not.
So, every value which occurred the first time is shifted towards the beginning and appended to the previous values which occurred the first time each. Additionally, the iterWrite points past the last written element after the loop and can be used to erase the rest (which contains left input values which are all duplicates).
The complexity of this algorithm is O(n) – much better than the naive approach.
Btw. the standard algorithms std::remove(), std::remove_if() does it the same way.
Thus, the same algorithm could be achieved with std::remove_if():
#include <algorithm>
#include <iostream>
#include <set>
#include <vector>
int main()
{
using Sample = std::vector<int>;
Sample sample = { 5, 2, 3, 3, 2, 2 };
// remove duplicates
using Table = std::set<int>;
Table table;
Sample::iterator last
= std::remove_if(sample.begin(), sample.end(),
[&](int value) { return !table.insert(value).second; });
sample.erase(last, sample.end());
// output the result
for (const Sample::value_type& entry : sample) {
std::cout << "Value " << entry << "\n";
}
}
Output:
like above
Demo on coliru
#include <iostream>
#include <set>
using namespace std;
int main()
{
multiset<int> p1;
p1.insert(5);
p1.insert(2);
p1.insert(3);
p1.insert(4);
p1.insert(2);
p1.insert(2);
for (auto iter = p1.begin(); iter != p1.end();)
{
p1.count(*iter) > 1 ? iter = p1.erase(iter) : iter++;
}
for (auto & iter : p1)
{
cout << iter << ", ";
}
return 0;
}

Why does std::lower_bound with greater-than compare function give no match?

Let's look at the following code:
#include <iostream>
#include <vector>
#include <algorithm>
int main()
{
std::vector<int> exmpl = { 2, 3, 6, 9, 134, 1143, 2345 };
auto LessThan = [](const int a, const int b) -> bool { return a < b; };
auto GreaterThan = [](const int a, const int b) -> bool { return a > b; };
auto it_reverse_less_than = std::lower_bound(exmpl.rbegin(), exmpl.rend(), 8, LessThan);
auto it_reverse_greater_than = std::lower_bound(exmpl.rbegin(), exmpl.rend(), 8, GreaterThan);
auto it_forward_less_than = std::lower_bound(exmpl.begin(), exmpl.end(), 8, LessThan);
auto it_forward_greater_than = std::lower_bound(exmpl.begin(), exmpl.end(), 8, GreaterThan);
std::cout << *it_reverse_less_than << "\n";
std::cout << *it_reverse_greater_than << "\n";
std::cout << *it_forward_less_than << "\n";
std::cout << (it_forward_greater_than == exmpl.end());
return 0;
}
It gives the following output:
2345
6
9
1
I've checked the results using VC++ compiler as well as gcc via Ideone (see the link below) and they are the same.
I think it_reverse_greater_than points to 1143 as it is the first element that the reverse iterator sees for which the compare function returns false. I don't understand though why the GreaterThan function doesn't return a match when used with forward iterators (it_forward_greater_than points to end()). Shouldn't it_forward_greater_than point to 2 as it is the first element for which the compare function returns false? Isn't it an analogical situation to it_reverse_greater_than pointing to the last element, i.e. the first one that the reverse iterator sees?
Here is the code in Ideone:
https://ideone.com/0z55ss

Sort multidimensional map based on pair value

I have a map defined as follows:
std::map<std::string, std::vector<std::pair<std::string, std::string>>> groupList;
GOAL:
I have a list of groups of commands. I want to sequence through each group in order. In order to do this, each group has a "sequence" value. I want to sort the overall list based on this value from smallest to largest. Essentially, I have a list of elements, which each have an accompanying list of other values within them specific to each parent. I want to sort the list of parents by a specific pair or value within the child lists.
For me to visualize this, I created an array in PHP with a similar structure. I'm not sure exactly how to visualize a C++ map so this is just me making assumptions. 1, 2, 3 are the keys of the map.
Array
(
[1] => Array
(
[groupID] => 1
[sequence] => 0
[command] => DefaultState
)
[2] => Array
(
[groupID] => 2
[sequence] => 2
[command] => Restart
)
[3] => Array
(
[groupID] => 3
[sequence] => 1
[command] => Beep
)
)
I'd like to sort this map based on the value of a particular pair inside, in this case "sequence". Element "2" should be below element "3" when sorting using the "sequence" value. The end result would look like this:
Array
(
[1] => Array
(
[groupID] => 1
[sequence] => 0
[command] => DefaultState
)
[3] => Array
(
[groupID] => 3
[sequence] => 1
[command] => Beep
)
[2] => Array
(
[groupID] => 2
[sequence] => 2
[command] => Restart
)
)
I'm sorry to mix languages here but there's no simple way for me to dump a map that depicts its structure (that I know of).
At first, my map was set up like so:
std::map<std::string, std::map<std::string, std::string>> groupList;
This was easier for me to add elements and then access them later, but I figured a vector pair would be easier to use for sorting. I would prefer to use the latter definition for ease. I was looking at using std::sort and boost but I've had no luck implementing for this specific case.
Any comments/help is welcome. Thanks!
Whenever I see a datastructure, I imagine to have a definition for it handy:
struct Item {
int groupID;
int sequence;
std::string command;
}
Now you can trivially define the array:
Item arr[] = {
{ 1, 0, "DefaultState" },
{ 2, 2, "Restart" },
{ 3, 1, "Beep" },
};
Since it's a simple aggregate with value semantics, you can just define an order and sort it:
struct Item {
int groupID;
int sequence;
std::string command;
bool operator<(Item const& other) const {
return sequence < other.sequence;
}
};
Demo
Adding a streaming operator<< and we get a full working demo:
Live On Coliru
#include <iostream>
struct Item {
int groupID;
int sequence;
std::string command;
bool operator<(Item const& other) const {
return sequence < other.sequence;
}
friend std::ostream& operator<<(std::ostream& os, Item const& i) {
return os << "Item { group:" << i.groupID << ", sequence:" << i.sequence << ", command:'" << i.command << "' }";
}
};
#include <algorithm>
int main() {
Item arr[] = {
{ 1, 0, "DefaultState" },
{ 2, 2, "Restart" },
{ 3, 1, "Beep" },
};
std::sort(std::begin(arr), std::end(arr));
for (auto& item : arr)
std::cout << item << '\n';
}
Prints
Item { group:1, sequence:0, command:'DefaultState' }
Item { group:3, sequence:1, command:'Beep' }
Item { group:2, sequence:2, command:'Restart' }
Expanding on my earlier answer, and in case you're really looking for something more advanced, here's what comes to mind using Boost MultiIndex containers:
Live On Coliru
#include <fstream>
#include <iostream>
struct Item {
int groupID;
int sequence;
std::string command;
friend std::ostream& operator<<(std::ostream& os, Item const& i) {
return os << "Item { group:" << i.groupID << ", sequence:" << i.sequence << ", command:'" << i.command << "' }";
}
};
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/member.hpp>
namespace bmi = boost::multi_index;
using Table = bmi::multi_index_container<
Item,
bmi::indexed_by<
bmi::ordered_unique<
bmi::tag<struct by_group>,
bmi::member<Item, int, &Item::groupID>
>,
bmi::ordered_unique<
bmi::tag<struct by_sequence>,
bmi::member<Item, int, &Item::sequence>
>
>
>;
#include <algorithm>
#include <map>
int main() {
Table arr = {
{ 1, 0, "DefaultState" },
{ 2, 2, "Restart" },
{ 3, 1, "Beep" },
};
for (auto& item : arr.get<by_group>())
std::cout << item << '\n';
std::cout << "\nsorted by sequence:\n";
for (auto& item : arr.get<by_sequence>())
std::cout << item << '\n';
}
Prints
Item { group:1, sequence:0, command:'DefaultState' }
Item { group:2, sequence:2, command:'Restart' }
Item { group:3, sequence:1, command:'Beep' }
sorted by sequence:
Item { group:1, sequence:0, command:'DefaultState' }
Item { group:3, sequence:1, command:'Beep' }
Item { group:2, sequence:2, command:'Restart' }

sorting a map based on value

Suppose I have a std::map<std::string,int> this map stores an id along with a debt amount.
I want to know if there was a way for me to obtain the 5 highest (int) values from the map.
I know I could iterate through the map and do a custom sort however is there a custom algorithm that might help me accomplish this ? What would the most efficient way be ?
Only if you save them in another place while inserting to the map and maintain it.
A map is just a map....
Getting the highest 5 will be o(N) on the map. if you manage them while inserting you can do it in o(1)
Create a map std::map<int, std::string> and insert all data from old map into this map.
Then 5 key from end of new map have the highest value.
you could build a vector of iterators into the map:
#include <algorithm>
#include <iostream>
#include <vector>
#include <map>
#include <algorithm>
using namespace std;
int main()
{
// make some data
using data_map_type = map<string, int>;
data_map_type data_map = {
{ "a", 10 },
{ "b", 5 },
{ "c", 3 },
{ "d", 3 },
{ "e", 4 },
{ "f", 1 },
{ "g", 2 },
{ "h", 5 },
{ "i", 0 },
{ "j", 2 },
{ "k", 1 },
};
// build reverse index (initially unordered)
std::vector<data_map_type::const_iterator> second_index;
for(auto it = begin(data_map) ; it != end(data_map) ; ++it)
second_index.push_back(it);
// order the secondary index by descending debt amount
sort(begin(second_index),
end(second_index),
[](const data_map_type::const_iterator& l,
const data_map_type::const_iterator& r)
{
return l->second > r->second;
});
// emit the top 5 (or fewer)
const auto limit = std::min(second_index.size(), size_t(5));
cout << "top " << limit << " entries:" << endl;
for(size_t i = 0 ; i < limit ; ++i)
{
cout << "key=" << second_index[i]->first << ", value=" << second_index[i]->second << endl;
}
return 0;
}

std::unordered_set insert, get the position where item was inserted

Assume I have class MyClass
class MyClass
{
public:
MyClass( std::string str ) : _str(str) {}
void SetPosition ( int i ) { _pos = i; }
std::string _str;
int _pos;
};
namespace std
{
template<> struct hash<shared_ptr<MyClass>>
{
size_t operator()( const shared_ptr<MyClass> & ptr ) const
{
return hash<string>()( ptr->_str ) + hash<int>()( ptr->_pos );
}
};
}
When using std::vector, I was able to do this:
std::string str = "blah";
auto ptr = std::make_shared<MyClass>( str );
std::vector<std::shared_ptr<MyClass>> vector;
vector.push_back( ptr );
ptr->SetPosition ( std::addressof( vector.back() ) - std::addressof( vector[0] ) );
std::cout << ptr->_str << " is at " << ptr->_pos << std::endl;
In order to calculate where in the vector, my object pointer was placed.
However, If I want to use std::unordered_set (which I do), then:
std::string str = "blah";
auto ptr = std::make_shared<MyClass>( str );
std::unordered_set<std::shared_ptr<MyClass>> set;
auto res = set.insert( ptr );
ptr->SetPosition ( std::addressof( res.first ) - std::addressof( set[0] ) );
std::cout << ptr->_str << " is at " << ptr->_pos << std::endl;
Will not work.
Neither will
std::addressof( set.begin() );
Nor will,
std::addressof( set.begin().first );
or any other way I try to use the front iterator.
Does this make sense? Or should I rely on set.size() and assume that my pointer was inserted at the end?
Is there any way to safely get the position where that pointer was inserted using something similar to the above code?
unordered_set, like the name implies, is unordered. You can keep track of the position of your elements in a vector, because as long as you don't erase anything, they won't change places. But that's not true of unordered_set. For instance, on my implementation, here's what printing all the elements in order after every insert would yield:
std::unordered_set<int> s;
s.insert(0); // 0
s.insert(1); // 1 0
s.insert(2); // 2 1 0
s.insert(3); // 3 2 1 0
...
s.insert(22); // 22 0 1 2 3 ... 19 20 21
...
s.insert(48); // 48 47 46 45 ... 22 0 1 2 3 4 ... 21
So what I'm trying to say is order is definitely not something that makes sense for you to rely on.
With your vector, however, you can do much better in terms of setting position:
vector.push_back(ptr);
ptr->SetPosition(vector.size() - 1);