Appending to map with set<size_t> in it - c++

Hey my mentor(kinda) gave me this task and i am having trouble solving it .
So basically i am getting const vector<string> &data full of string and i need to check their place, like where are they in that vector so here is an example:
getting an input with: data={"chair","desk","table","chair","chair","desk"}
my output should be:{"chair" ->{0,3,4},"desk"->{1,5} , "table"->{2}}
so what i did is :
map<string, set<size_t>> index(const vector<string> & data) noexcept {
map<string, set<size_t>> res; // thats gon be my return
if (data.empty()){
return res;
}
for (auto it = data.begin(); it != data.end(); it++) {
if(res.find(*it)==res.end()){
//so basically when we are the first , and nothing is there so
// it should insert for example =chair , and a 0
res.insert(std::make_pair(*it, 0)); // i tried it like that it doesnt work
}else{
// when the string "chair is already in there i want to append to the set the current index where we are and thats what i dont know how
}
}
return res;
}
how to get the index of the current string and append it to my set<size_t> so that is works as mentioned?

You can implement this quite easily by using an index-based loop:
map<string, set<size_t>> index(const vector<string> & data)
{
map<string, set<size_t>> res;
for (std::size_t i = 0u; i < data.size(); ++i)
res[ data[i] ].insert(i);
// ^string^ ^index
return res;
}

#include <iostream>
#include <string>
#include <vector>
#include <set>
#include <map>
std::map<std::string, std::set<size_t>> index(const std::vector<std::string>& data) noexcept
{
std::map<std::string, std::set<size_t>> res;
for (size_t idx = 0u; idx < data.size(); idx++)
{
res[data[idx]].insert(idx);
}
return res;
}
int main()
{
const std::vector<std::string> data={"chair","desk","table","chair","chair","desk"};
auto map = index(data);
for( auto it = map.begin(); it != map.end(); ++it )
{
std::cout << (*it).first << " ->";
for( auto it_id = (*it).second.begin(); it_id != (*it).second.end(); it_id++)
{
std::cout << " " << *it_id;
}
std::cout << "\n";
}
}
So your problem can be solved with a oneline as seen in the index function - but why ?
There is a reason youre getting all these different std container types as input - each of them have some guarantees as can be looked up in the documentation.
vector elements are continous in memory (sequence container).
while map and set are associative and have unique keys.
Consult the documentation for container properties (i linked to the vector - but the rest are also present in the tree to the left).
http://www.cplusplus.com/reference/vector/vector/
The lesson is knowing which tool(container) to use to solve specific tasks
And once you have that nailed .. making sure items are unique or how to sequence every odd element in a vector should be a breeze.

Related

How to delete specific elements in a vector using struct data type

i'm new to C++. My program is a quiz game which user can choose category and level for the questions. At first, i use the struct data type
struct QuestionInfo
{
string category;
string level;
string question;
string answer;
};
then
vector<QuestionInfo> vec;
The idea of this part is to store the info of the question include (category, level, question and answer) to each element.
Then after building menu and the output questions UI, i go to the filters
void category_filter()
{
for (unsigned int i = 0; i < vec.size(); i ++)
{
if (category_choice != vec[i].category)
vec.erase(vec.begin() + i );
}
}
Void level_filter()
{
for (unsigned int i = 0; i < vec.size(); i ++)
{
if (level_choice != vec[i].level)
vec.erase(vec.begin() + i );
}
}
So the idea of the filters is to delete the elements which not contain the matched category and level. But the output questions did not match with the category and the level i had choose before. I'm not sure what I'm doing wrong.
Let me explain you the problem with my example. Suppose you have a vector of 10 elements, valid indexes are 0 till 9 elements. You have to erase 5th element i == 4. You erase it, then 6th element with index 5 moves to place of 5th elements with index 4. After that you increase i in for, it becomes 5. Thus you skip previous 6th element, that is now 5th with index 4.
You may fix your code like below, moving i ++ to the condition.
for (unsigned int i = 0; i < vec.size(); ) {
if (category_choice != vec[i].category)
vec.erase(vec.begin() + i );
else
i ++;
}
The preferable solution in C++ way is demonstrated by #Jonathan.
You're getting tripped up by not accounting for the indexing shift that occurs when you erase an element. I personally would rely on remove_if and erase with a lambda to accomplish this:
vec.erase(remove_if(begin(vec), end(vec), [&](const auto& i) { return category_choice != i.category; }, end(vec));
vec.erase(remove_if(begin(vec), end(vec), [&](const auto& i) { return level_choice != i.level; }, end(vec));
Alternatively you might consider combining them for a bit of speed improvement:
vec.erase(remove_if(begin(vec), end(vec), [&](const auto& i) { return category_choice != i.category || level_choice != i.level; }, end(vec));
You might want to remove_if + erase:
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
int main()
{
struct QuestionInfo
{
std::string category;
std::string level;
std::string question;
std::string answer;
QuestionInfo(std::string category, std::string level, std::string question, std::string answer) :
category(category), level(level), question(question), answer(answer) {}
};
std::vector<QuestionInfo> vec;
std::string category_choice = "cat1";
std::string level_choice = "lev1";
vec.push_back(QuestionInfo("cat1", "lev1", "q1", "a1"));
vec.push_back(QuestionInfo("cat1", "lev2", "q2", "a2"));
vec.push_back(QuestionInfo("cat2", "lev1", "q3", "a3"));
vec.push_back(QuestionInfo("cat2", "lev2", "q4", "a4"));
std::cout << "\nNot filered" << std::endl;
for (auto const &info : vec)
std::cout << "Category:" << info.category << " Level:" << info.level << std::endl;
auto filter_category = std::remove_if(vec.begin(), vec.end(), [&](auto const &info) {return category_choice != info.category; });
vec.erase(filter_category, vec.end());
std::cout << "\nFilered by category" << std::endl;
for (auto const &info : vec)
std::cout << "Category:" << info.category << " Level:" << info.level << std::endl;
auto filter_level = std::remove_if(vec.begin(), vec.end(), [&](auto const &info) {return level_choice != info.level; });
vec.erase(filter_level, vec.end());
std::cout << "\nFiltered by level" << std::endl;
for (auto const &info : vec)
std::cout << "Category:" << info.category << " Level:" << info.level << std::endl;
system("pause");
return 0;
}
As mentioned by others, the remove_if + erase is a standard and expressive way to achieve what you want. But you may also consider non-destructive filtering with a copy_if into a new container, or even without using any additional storage with Boost.Range adaptor boost::adaptors::filtered or boost::filter_iterator. Look here for examples.

Copy subset of a std::map

I am trying to copy a subset of a std::map structure to a new map structure in the most efficient way. I can only think to a plain vanilla solution like this:
// Example program
#include <iostream>
#include <string>
#include <map>
int main()
{
// build map
std::map<int, int> mymap;
size_t num_el = 10;
for(size_t i = 0; i < num_el; ++i)
{
mymap.insert(std::pair<int,int>(i,i));
}
// copy submap
int start_index = 5;
std::map<int,int> output_map;
std::map<int,int>::const_iterator it;
for(it = mymap.find(start_index); it != mymap.end(); ++it)
{
output_map.insert(*it);
}
//print result
std::map<int,int>::const_iterator pit;
for(pit = output_map.begin(); pit != output_map.end(); ++pit)
{
std::cout << pit->second << " , ";
}
std::cout << std::endl;
}
Is there a better way to do this?
The insert method allows you to specify a range like so:
auto range_start = mymap.find(5);
auto range_end = mymap.end();
output_map.insert(range_start, range_end);
If you want additional flexibility on choice of the copied elements, copy_if will provide it:
std::copy_if(
begin(mymap),
end(mymap),
std::inserter(output_map, begin(output_map)),
[&start_index](std::pair<int,int> p) { return p.first >= start_index; }
);
It's a bit tricky to use it for std::map (and also e.g. std::set), but std::inserter works nicely.
Of course the simpler std::copy could be used similarly to the range constructor.
You can use map range constructor (c++11):
std::map<int,int> output_map{mymap.find(start_index), mymap.end()};
and if you need older standard:
std::map<int,int> output_map( mymap.find(start_index), mymap.end() );
And insert works too

Iterating over a vector of maps, and inserting a copy if a condition is satisfied

I'm new to stl in c++ and I want to iterate over a vector of maps. Then, if one of these maps satisfies a certain condition, I want to make a copy of this map an then insert the copy into the vector.
For example:
int main(){
vector<map<string, int> > my_vector;
map<string, int> my_map;
my_map["zero"] = 0;
my_vector.push_back(my_map);
for(vector<map<string, int> >::iterator iter1 = my_vector.begin();
iter1 != my_vector,end();
iter1++){
for(map<string, int>::iterator iter2 = (*iter1).begin();
iter2 != (*iter1).end();
iter2++){
if(iter2->second == 0){
// make a copy of the map (*iter1), make some changes on it,
// then insert the copy in the vector
}
}
}
return 0;
}
I have tried with:
map<string, int> new_map = *iter1;
new_map["zero"]++; // to avoid an infinite loop
my_vector.push_back(new_map);
But the program crashes. No compiler error, only a program crash.
Then I found this question and answers in stackoverflow, so I tried another way like this:
int main(){
vector<map<string, int> > my_vector;
map<string, int> my_map;
my_map["zero"] = 0;
my_vector.push_back(my_map);
int remaining = my_vector.size();
int current_position = 0;
while(remaining>0){
for(map<string, int>:: iterator iter1 = my_vector[index].begin();
iter1 != my_vector[index].end();
iter1++){
if(iter1->second == 0){
map<string, int> new_map = my_vector[0];
new_map["zero"]++; // to avoid an infinite loop
my_vector.push_back(new_map);
}
}
index++;
remaining = my_vector.size()-index;
}
return 0;
}
But the program is still crashing, so I think the problem (or one of the problems) would be not only the iterator, but also the "copy" of the map.
If anyone have an idea of how I should do this, I will really appreciate it.
Your program may crash because push_back can invalidate iterators. For example if a call to push_back leads to memory reallocation (it happens when you exceed current capacity of the vector) then all vector's iterators become invalid (they point to deallocated memory).
To solve that problem you can use indexes instead of iterators to access the vector's elements or you can push_back new elements to another copy of the vector.
In other words: don't use an old vector::iterator after you push_back'ed a new element into the vector.
One way you can do is:
size_t orig_size = my_vector.size();
for( size_t i = 0; i < orig_size; i++ ) {
//...
}
Other way:
Build a new vector and then afterwards append the contents of this new vector to the original vector.
I probably have the wrong idea, but I think you can do something like this. You iterate over the elements of the vector, use the predicate to see if a current element fits the criteria, then use back_inserter.
#include <iostream>
#include <map>
#include <vector>
#include <algorithm>
#include <iterator>
using namespace std;
int main(){
vector<map<string, int> > my_vector;
map<string, int> my_map;
my_map["zero"] = 0;
my_vector.push_back(my_map);
auto predicate = [](const map<string, int>& cur) {
for (const auto& pair : cur)
{
if (pair.second == 0)
return true;
}
return false;
};
std::copy_if(my_vector.begin(), my_vector.end(), back_inserter(my_vector), predicate);
return 0;
}
This is my solution. It uses C++11 (because it looks so much better). Especially with collections C++11 gives the workflow a real boost.
#include <vector>
#include <string>
#include <map>
#include <iostream>
using namespace std ;
void merge(vector<map<string,int>>& from, vector<map<string,int>>& to)
{
for(auto entry : from) {
to.push_back(entry) ;
}
}
void search(vector<map<string,int>>& vec)
{
int iCount = 0 ;
vector<map<string, int>> cVector ;
for(auto vmap : vec) {
for(auto mapentry : vmap) {
if(mapentry.second == 0) {
iCount++ ;
map<string, int> new_map = vmap ;
cVector.push_back(new_map) ;
}
}
}
cout << "iCount: " << iCount << endl ;
merge(cVector, vec) ;
}
int main(){
vector<map<string, int> > my_vector;
map<string, int> my_map;
my_map["zero"] = 0;
my_map["third"] = 2 ;
my_map["second"] = 0 ;
my_vector.push_back(my_map);
cout << my_vector.size() << endl ;
vector<map<string,int>> cVector ;
search(my_vector) ;
cout << my_vector.size() << endl ;
return 0;
}
As written by Alex Antonov the push_back invalidates your Iterator because the memory gets reallocated. My answer creates a new vector and copies the entries after the search back in the original vector (my_vector).
About range base for-loops see this.

Copying C++ Map into key and value vectors

I have a map and I want the first column i.e (*it).first to be pushed back into a vector then (*it)->second to be pushed back into another vector
Is this the best way to do it?
std::vector<std::string>test;
for ( it=mymap.begin() ; it != mymap.end(); it++ )
{
test.push_back((*it).first);
}
My other question is if i have a loop i.e
how would I insert all the integers i into (*it).first?
for(int i = 0; i < 10; i++)
{
// 1 - 10 will go in (*it).first
}
I want to have some integers in (*it).first and have associated values in (*it).second;
Use std::transform.
First define two functions key and value which take the pair of strings and return the first or second value, respectively.
#include <map>
#include <vector>
#include <algorithm>
#include <iostream>
#include <iterator>
const std::string& key(const std::pair<std::string, std::string>& keyValue)
{
return keyValue.first;
}
const std::string& value(const std::pair<std::string, std::string>& keyValue)
{
return keyValue.second;
}
Then use std::transform from <algorithm> with the functions to transform the map into either a vector of keys or a vector of values.
int main()
{
using namespace std; // be explicit normally, trying to be brief here
map<string, string> contacts;
contacts["alice"] = "555-2701";
contacts["bob"] = "555-2702";
vector<string> keys(contacts.size());
vector<string> values(contacts.size());
transform(contacts.begin(), contacts.end(), keys.begin(), key);
transform(contacts.begin(), contacts.end(), values.begin(), value);
cout << "Keys:\n";
copy(keys.begin(), keys.end(), ostream_iterator<string>(cout, "\n"));
cout << "\n";
cout << "Values:\n";
copy(values.begin(), values.end(), ostream_iterator<string>(cout, "\n"));
return 0;
}
Output:
Keys:
alice
bob
Values:
555-2701
555-2702
Your first question, "how can I push the first column of my map into one vector and the 2nd column into another" is solved thus:
std::map<std::string, std::string> mymap;
std::vector<std::string> keys;
std::vector<std::string> values;
for ( std::map<std::string,std::string>::iterator it=mymap.begin() ; it != mymap.end(); ++it )
{
keys.push_back(it->first);
values.push_back(it->second);
}
Your second question, "how would insert all the integers i into (*it).first ?" is solved thus:
std::map<int, int> mymap2;
for(int i = 0; i < 10; i++)
{
// Insert default value into map
// This sets '(*it).first' to 'i' and
// '(*it).second' to a default value (in
// this case, 0).
mymap2[i];
}
or
std::map<int, int> mymap3;
for(int i = 0; i < 10; i++)
{
// Insert specified value into map
// this sets '(*it).first' to 'i', and
// '(*it).second' to the value returned from the function.
maymap3[i] = ChooseSpecificValue(i);
}
Well, it can be done with a simple loop:
for (auto const& p: mymap) {
vec1.push_back(p.first);
vec2.push_back(p.second);
}
Or using the std::transform algorithm, though it's quite verbose here:
std::transform(mymap.begin(), mymap.end(), std::back_inserter(vec1),
[](MyMap::const_reference p) { return p.first; });
Assuming you've declared your map as string key and value (ie map<string, string> mymap; then it would be like below, also assuming you've declare 'it' variable as map<string, string>::iterator it, etc:
std::vector<std::string> test;
std::vector<std::string> second;
std::map<string, string>::iterator it;
for ( it=mymap.begin() ; it != mymap.end(); it++ )
{
test.push_back((*it).first);
second.push_back((*it).second);
}
Not sure about your next question.
The first part of your question:
std::vector<std::string> test;
std::vector<std::string> test2; // assuming map is from string to string
for (it = mymap.begin(); it != mymap.end(); ++it)
{
test.push_back(it->first); // push first in one vector
test2.push_back(it->second); // push second in another vector
}
So, yes a simple for can do what you want.
The second part of your question:
Since you are updating the key of the map, you would need to remove it from the map and insert the changed one. So:
std::string first, second;
first = it->first;
second = it->second;
mymap.erase(it); // be careful with invalidating iterator
// change first
mymap[first] = second;
To change first by adding all integers i to it, that would really depend on the type of first. For example with a string, you may mean something like this:
ostringstream sout;
for (int i = 0; i < 10; ++i)
sout << (i?" ":"") << i;
first = sout.str();
Or if first is for example a set, you may mean something like this:
for (int i = 0; i < 10; ++i)
first.insert(i);
and my other question is if i have a loop i.e how would insert all the
integers i into (*it).first?
In the case of a std::map, you can't modify the iterator returned like that ... the key member (i.e., the first) in the std::map key/value pair data-structure is intentionally designated as a constant value, and is initialized to its constant value at the beginning of the key/value pair's lifetime in the std::map data-structure. If the keys weren't constant, you would end up creating havoc when you change the key, since the nodes in a std::map are suppose to be sorted by the keys. The second member of the key/value pair data-structure is the member that can be changed.
So if you want to insert a set of key/value pairs in a map, you could simply do the following:
std::map<int, int> mymap;
int some_other_value = 100;
for (int i=0; i < 10; i++)
{
mymap[i] = some_other_value++;
}
it here will be an iterator which will point to one of the position in map and at max have one first and second value for one iterator . At max you can have multiple key or same key holding same/different values depending on key/value combination.
As far as pushing the value in the vector for a key in map is concern you can do it in the same way you are pushing the key
std::vector<std::string>test;
std::vector<std::string>test2;
for ( it=mymap.begin() ; it != mymap.end(); it++ )
{
test.push_back((*it).first);
test2.push_back((*it).second);
}
Neways yours question is very unclear .
Just in case you want to deal with different data types in your map I would template a generic copy function:
template <class A, class B>
void mycopy(std::map<A, B>&m, std::list<A>& keys, std::list<B>& values) {
typename std::map<A, B>::iterator it;
for (it = m.begin(); it != m.end(); ++it) {
keys.push_back( (*it).first );
values.push_back( (*it).second );
}
}
Mixing it up:
std::map<int, std::string> mymap;
std::list<int> keys;
std::list<std::string> values;
mymap[1] = "string1";
mymap[2] = "string2";
mycopy(mymap, keys, values);
std::map<std::string, int> mymap1;
std::list<std::string> keys1;
std::list<int> values1;
mymap1["string1"] = 1;
mymap1["string2"] = 2;
mycopy(mymap1, keys1, values1);
Edit: yes __copy isnt the best definition. Thanks

count of elements in a set (not total count)

I have a set of strings in c++.
i am inserting into that set as :
m.insert("1-2-35-2");
m.insert("1-2-36-1");
m.insert("1-2-37-2");
m.insert("1-2-38-1");
m.insert("1-2-39-2");
m.insert("2-2-40-1");
m.insert("2-2-41-2");
m.insert("2-2-42-1");
m.insert("1-2-43-2");
m.insert("1-2-44-1");
m.insert("1-2-45-2");
m.insert("1-2-46-1");
m.insert("1-2-47-2");
i want to calculate the count of all the strings inside the set which start with "2-"(count =3) and also which start with "1-"(count=10).
is there any way to do it.
I tried with lower_bound and upper_bound but its giving me some errors.
errors are coming for the statement:
int i=it_upper-it_lower;
I am using solaris SPARC OS.
i just tested this program
#include <iostream>
#include <iterator>
#include <list>
using namespace std;
int main () {
list<int> mylist;
for (int i=0; i<10; i++) mylist.push_back (i*10);
list<int>::iterator first = mylist.begin();
list<int>::iterator last = mylist.end();
cout << "The distance is: " << distance(first,last) << endl;
return 0;
}
it gives me compilation error:
line 13: Error: Could not find a match for std::distance<std::ForwardIterator, std::Distance>(std::list<int, std::allocator<int>>::iterator, std::list<int, std::allocator<int>>::iterator).
1 Error(s) detected.
Sorry. Wrong answer
Update:
count_if is an algorithm to count elements based on function. Try like in this example:
bool struct key_part: public std::unary_function< std::string, bool >
{
std::string _part;
key_part(const std::string part):_part(part){}
bool operator()(std::string &s)
{
return s.find(_part)!=std::string::npos;
}
}
std::count_if( m.begin(), m.end(), key_part("1-") );
It will count all elements that contains "1-" as part of key
If you have a modern compiler that supports lambdas, you could use those as the predicate to count_if:
auto if_s_1 = [](const std::string &s) { return s.find("1-") == 0; }
auto if_s_2 = [](const std::string &s) { return s.find("2-") == 0; }
int count1 = std::count_if(m.begin(), m.end(), if_s_1);
int count2 = std::count_if(m.begin(), m.end(), if_s_2);