Print value of multiple equal keys once time C++ - c++

Good morning,
I have a multiple map defined in this way
multimap<string, string> H_test;
with value insered from user(multiple book written by the same author in a random order)
H_test.insert(pair<string, string>(author, book));
I would like to have as output something like that :
Author I // one author
Book A // all book written by this author
Book B
Author II
Book K
Author III
Book C
Book D
Book E
Using this code
for (multimap<string,string>::iterator it = H_test.begin(); it != H_test.end(); it++)
{
//if ()
{
pair <multimap<string, string>::iterator, multimap<string, string>::iterator> ret;
ret = H_test.equal_range(it->first);
std::cout << it->first << endl;
for (multimap<string, string>::iterator sd = ret.first; sd != ret.second; sd++)
{
cout << "\t" << sd->second << endl;
}
}
}
I obtain this output
Author I
Book A
Book B
Author I
Book A
Book B
Author II
Book K
Author III
..
In my opinion, it prints the name of first key (and all the books which have the same author) and then it goes to the second nome (which has the same name), and re-write everything.
Do you know how to solve it?
Thanks

upper_bound is your friend. You'd want to use it something like this:
auto start = begin(H_test);
while (start != end(H_test)) {
auto finish = H_test.upper_bound(start->first);
cout << "Author: " << start->first << endl;
for_each(start, finish, [](const auto& i) { cout << "Book: " << i.second << endl; });
start = finish;
}
Live Example
Note: You can use cbegin and cend instead of if H_test is const.
EDIT:
It helps me to conceptualize a multimap as a sorted array of pairs, where the first element in the pair is the key and the second element is the value. Here's effectively what we'll do:
Iterate through all the pairs using start as our iterator, beginning at the first pair: auto start = begin(H_test) and going to the last pair in the multimap: while(start != end(H_test))
Next we want to find the range of pairs in the container that share the same key, for that we'll find an iterator pointing one past the end using the upper_bound function that I linked the details about above: auto finish = H_test.upper_bound(start->first)
We will print the current author who's books we just found in the range: cout << "Author: " << start->first << endl
Then we'll print each book in the range defined by start to finish using for_each and a lambda to only print the value of the pair then move to the next line: for_each(start, finish, [](const auto& i) { cout << "Book: " << i.second << endl; });
Finally we'll set start to the beginning of the next range, which will be printed next time through the loop: start = finish

Simply iterate through the multimap and since all the elements with the same author are grouped together, you just need to detect a change in author to know you have moved on to the next author at which time you print the name of the author. Preset the lastAuthor to something that won't potentially match a real author. This executes in O(n)
string lastAuthor = "**SENTINEL**";
for (const auto& elem : H_test)
{
if(lastAuthor != elem.first)
{
cout << "Author: " << elem.first << '\n';
lastAuthor = elem.first;
}
cout << "Book: " << elem.second << '\n';
}

Related

Effectively getting items from map based on specific sort

I have a fairly easy problem: I have an std::map<int,T> and another std::set<int> (can be std::vector or similar too).
In the map I store items, and in the other container I'm storing favorites (of the map).
At some point, I'd need to retrieve (all) items from the map, but starting with the favorites defined by the other container.
Here is my minimal repro, I solved it very ugly, and ineffective:
#include <iostream>
#include <string>
#include <set>
#include <map>
using namespace std;
map<int, string> myMap;
set<int> myFavorites;
int main()
{
myMap.emplace(1, "but I don't like this");
myMap.emplace(12, "So it will go below");
myMap.emplace(31, "This one will come first, and");
myMap.emplace(44, "under my favorites");
myMap.emplace(52, "then this will follow");
myFavorites.insert(52);
myFavorites.insert(31);
cout << "My map:" << endl;
for(auto p : myMap) {
cout << "#" << p.first << "=" << p.second << endl;
}
cout << endl << "My favorites:" << endl;
for(auto p : myFavorites) {
cout << "#" << p << endl;
}
cout << endl << "All items starting with my favorites:" << endl;
for(auto p : myFavorites) {
auto item = myMap.find(p);
if (item != myMap.end()) cout << "#" << item->first << "=" << item->second << endl;
}
for(auto p : myMap) {
if (myFavorites.find(p.first) != myFavorites.end()) continue;
cout << "#" << p.first << "=" << p.second << endl;
}
}
What really bothers me is the last loop, where each iterations would call find on the set.
Required output is:
All items starting with my favorites:
#31=This one will come first, and
#52=then this will follow
#1=but I don't like this
#12=So it will go below
#44=under my favorites
Here is the above source in Coliru for making it easier: https://coliru.stacked-crooked.com/a/731fa76d90bfab00
Both map and set might be changed, but replacements needs to implement the same interfaces as originals.
I'm looking for a way to solve this more efficient than my original "brute-force" one.
Please note: map must not be "reordered"! I just need to query (retrieve) its items with custom sorting!
Note2: I know map can have a comparison operator. But I'd need to have the original order usually, and sometimes I'd need to have the custom sort!
Note3: Boost is not available and compiler is C++14 capable.
Both std::map and std::set use the same strict weak ordering for ordering its contents.
You can take advantage of this. You know that if you iterate over the map you will get the keys in the same order as they are in the set, therefore all it takes is a little bit of clever logic, something like:
auto map_iter=myMap.begin();
for(auto p : myFavorites) {
while (map_iter != myMap.end())
{
if (map_iter->first == p)
cout << "#" << map_iter->first << "=" << map_iter->second << endl;
if (map_iter->first > p)
break;
++map_iter;
}
}
It may still make sense to use find() in some edge cases, specifically when myFavorites is significantly smaller than myMap, in which case a few calls to find() might be faster than iterating over (most of) the entire map.

How to remove only one element from a vector with duplicates

I am making a D&D game in C++. I roll 6 scores randomly, put them in a vector, display them to the player. Then I go through each ability (str, dex, con, int, wis, and cha), and call a function that asks the player which of the scores they want to use for each ability, and then i remove it from the vector, return the value, and move on to the next ability. It works fine unless there is a duplicate roll, in which case it deletes both of the duplicates. I want it to only remove one at a time regardless of duplicates, and I haven't been able to find anything online to do this. Here is the function call
int Character::initScores(std::vector<int> & v, std::string ability)
{
int c = 0;
bool error = 0;
do {
if (c != 0) {
std::cout << "That isn't one of your scores. Try again. " <<
std::endl;
}
int choice;
std::cout << ability << ": ";
std::cin >> choice;
if (std::find(v.begin(), v.end(), choice) != v.end())
{
v.erase(std::remove(v.begin(), v.end(), choice), v.end());
std::cout << "Your remaining rolls are ";
for (int i = 0; i < v.size(); i++)
std::cout << v[i] << " ";
std::cout << std::endl;
return choice;
}
else
{
c++;
error = 1;
}
} while (error = 1);
}
And the function calls
std::cout << "Enter which score you want for... " << std::endl;
strength = initScores(scores, "Strength");
dexterity = initScores(scores, "Dexterity");
constitution = initScores(scores, "Constitution");
intelligence = initScores(scores, "Intelligence");
wisdom = initScores(scores, "Wisdom");
charisma = initScores(scores, "Charisma");
Also please lmk if there is anything inefficient/bad practice in my code, I have only recently started working on my own coding projects
You are calling std::remove(), which "removes" ALL matching values from the container (really, it just moves them to the end of the container), and then you are calling the 2-parameter overload of the erase() method to physically delete ALL of the "removed" values from the container.
If you just want to remove 1 element, pass the iterator returned by std::find() to the 1-parameter overload of the erase() method:
auto iter = std::find(v.begin(), v.end(), choice);
if (iter != v.end())
{
v.erase(iter);
...
}

Using lower_bound on nested map

I have a map that looks like
map<string , map<int,int>>
the string contains name of a student, the nested map contains ID as key and age as value. When I print the map, it al prints values as it should.
However, I want to find a students with a certain ID and lower. I tried using lower_bound using:
for( auto &x : class ){
auto it = x.second.upper_bound(some_number);
for( ; it != x .second.begin() ; --it){
cout << x.first << " = " << << it -> first << " " <<it -> second << endl;
}
}
This indeed prints right names of students, but their IDs and ages are just zeros or random numbers, what is causing this behavior? It works when I just print it.
I tried to found out about this on cpp map reference but found nothing.
Following code solves your problem:
for( auto &x : Class ){
auto it = x.second.upper_bound(some_number);
while(it!=x.second.begin()){
it=prev(it);
cout<< x.first<< " = "<< it->first<< " "<< it->second<< endl;
}
}
Refer std::map::upper_bound
What above code does is, first it finds the iterator with id strictly greater than some_number. Now because we want to print "students with a certain ID and lower", we print all the id's lower than the return value of upper_bound.
The stopping condition is that if iterator is itself x.second.begin(), that means now we don't have any id's smaller than it.
Plus your data structure is strange, you should have student ID as your primary index.
map<int, pair<string,int> > would be more appropriate data structure. (Assuming unique id's which is mostly the case).
Although, you could do lot better using OOP concepts.
What you see is probably undefined behaviour, std::map::upper_bound returns also end iterator under some conditions and from your code it does not look like you check for this condition. Also you should not use class keyword as variable name for your map, I am preety sure it does not compile. Below is a sample code that should work with no UB and print all IDs less than some number including this ID:
http://coliru.stacked-crooked.com/a/efae1ae4faa3e656
map< string , map<int,int>> classes ={
{ "k1", {{1,1},{2,2},{3,3}} }
};
//int class;
int some_number = 4;
for( auto &x : classes ){
auto it_num_end = x.second.upper_bound(some_number); // some numberis just variable that contains number
for( auto it = x.second.begin(); it != it_num_end ; ++it){
cout << x.first << " = " << it -> first << " " <<it -> second << endl;
}
}

Is there a limit on the size of std::set::iterator?

I have a std::set of strings and I want to iterate over them, but the iterator is behaving differently for different sizes of set. Given below is the code snippet that I'm working on:
int test(set<string> &KeywordsDictionary){
int keyword_len = 0;
string word;
set<string>::iterator iter;
cout << "total words in the database : " << KeywordsDictionary.size() << endl;
for(iter=KeywordsDictionary.begin();iter != KeywordsDictionary.end();iter++) {
cout << *iter;
word = *iter;
keyword_len = word.size();
if(keyword_len>0)
Dosomething();
else
cout << "Length of keyword is <= 0" << endl;
}
cout << "exiting test program" << endl;
}
The code is working properly & *iter is being dereferenced & assigned to word until the size of KeywordsDictionary is around 15000. However when the size of KeywordsDictionary increases beyond 15000,
the print statement cout << *iter; is printing all the contents of KeywordsDictionary correctly.
but the pointer to the iterator *iter is not being dereferenced & not being assigned to word. word is just being an empty string.
EDIT: And the output of the program is :
total words in the database : 22771
�z���AAAADAAIIABABBABLEABNABOUTACACCEPTEDACCESSACCOUNT...
Length of keyword is <= 0
exiting test program
So basically, I'm guessing the loop is executing only once.
Try to declare keyword_len as
std::string::size_type keyword_len = 0;
instead of
int keyword_len = 0;

multimap iterator not working

I have a Playlist class that has a vector with Tracks and each Track has a multimap<long, Note> as datamember.
class Track {
private:
multimap<long, Note> noteList;
}
Using an iterator to acces the tracks is no problem, so this part here is working fine:
vector<Track>::iterator trackIT;
try{
for(noteIT = trackIT->getNoteList().begin(); noteIT != trackIT->getNoteList().end(); noteIT++){
cout << "---" << noteIT->second.getName() << endl;
}
}catch (int e){
cout << "exception #" << e << endl;
}
What I want to do next is iterate the Notes of each Track. But starting from this part all output is stopped. So I only get to see the first tracks name. Any cout's after that are not shown and the compiler isn't giving me any errors. Even the cout inside the try catch block isn't working..
vector<Track>::iterator trackIT;
multimap<long, Note>::iterator noteIT;
for(trackIT = this->playlist.getTracklist().begin(); trackIT < this->playlist.getTracklist().end(); trackIT++){
cout << trackIT->getTrackName() << endl;
for(noteIT = trackIT->getNoteList().begin(); noteIT != trackIT->getNoteList().end(); noteIT++){
cout << "---" << noteIT->second.getName() << endl;
}
}
cout << "random cout that is NOT shown" << endl; // this part doesn't show up in console either
Also, the method in my Track class that I'm using to add the Note objects looks like this:
void Track::addNote(Note &note) {
long key = 1000009;
this->noteList.insert(make_pair(key, note));
}
// I'm adding the notes to the track like this:
Note note1(440, 100, 8, 1, 1);
note1.setName("note1");
synthTrack.addNote(note1);
Any ideas why the iterator won't work?
Change
noteIT < trackIT->getNoteList().end()
To
noteIT != trackIT->getNoteList().end()
Not all iterators support less than / greater than comparisons.
If you have c++11 you can use a range-based for loop:
for (Note& note : trackIT->getNoteList())
Or you can use BOOST_FOREACH
BOOST_FOREACH (Note& note, trackIT->getNoteList())
You haven't shown the definitions of getTrackList or getNoteList, but there's a common mistake people make - if you return a copy of the container instead of a reference to it, the iterators will be pointing to different containers making comparisons impossible. Not only that but since the containers are temporary any use of the iterators results in undefined behavior.
If you are really hardcoding the track key, then there will only ever be one track in the map because std::map stores unique keys...
long key = 1000009; //If yo are really doing this, this key is already inserted so it will fail to insert more.
Also, if you would like a more elegant approach you could use function object.
struct print_track
{
void operator()(const Track& track)
{
cout << track.getTrackName() << endl;
std::for_each(track.getNoteList().begin(), track.getNoteList().end(), print_track_name());
}
};
struct print_note_name
{
void operator()(const std::pair<long,Note>& note_pair)
{
cout << "---" << note_pair.second.getName() << endl;
}
};
//In use...
std::for_each(playlist.getTracklist().begin(), playlist.getTracklist.end(), print_track());