std::partition to partition a a vector of string - c++

I am trying to use std::partition to partition a vector into multiple part based on whitespace.
void solution2()
{
std::vector<string> v{ "10","20","","30","40","50","","60","70" };
auto i = begin(v);
while (i != end(v)-1)
{
auto it = std::partition(i, end(v)-1, [](auto empty) {return empty != ""; });
std::copy(i, it, std::ostream_iterator<int>(std::cout, " "));
i = it;
}
}
For example in the above code I want to partition it into multiple and condition to partition is whitespace ""
so the vector v should partition to 3 groups
"10" "20"
"30" "40" "50"
"60" "70"
The problem I am facing is in line
auto it = std::partition(begin(v), end(v)-1, [](string empty) {return empty != ""; });
Error
Severity Code Description Project File Line Suppression State
Error C2679 binary '=': no operator found which takes a right-hand operand of type 'std::basic_string<char,std::char_traits<char>,std::allocator<char>>' (or there is no acceptable conversion) C:\source\repos\out\build\x64-debug\sample C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.34.31933\include\xutility 3919
Any suggestion what can be changed to fix the error.
Any other efficient way to do the same using std::range or std::view

With C++20, this can be done trivially with ranges::split_view:
std::vector<std::string> v{ "10","20","","30","40","50","","60","70" };
for(auto part : v | std::views::split(""))
{
for(auto num : part) std::cout << num << ',';
std::cout << '\n';
}
Demo

Using std::find to get the next empty string would be more appropriate here.
auto i = begin(v), e = end(v);
while (i != e) {
auto it = std::find(i, e, "");
std::copy(i, it, std::ostream_iterator<std::string>(std::cout, " "));
std::cout << '\n';
if (it == e) break;
i = it + 1;
}

Related

Iterate using iterators on nlohmann::json? Error: invalid_iterator

Continuing my previous question here, Now I want to insert the keys and values present in the below json into a std::vector<std::pair<std::string, std::vector<uint64_t>>> vec;
Keys here are this strings: 12CUDzb3oe8RBQ4tYGqsuPsCbsVE4KWfktXRihXf8Ggq , 12ashmTiFStQ8RGUpi1BTCinJakVyDKWjRL6SWhnbxbT
values corresponding them are list:[20964,347474, 34747],[1992,1993,109096]
This is the json which is response from query.
j = {
"12CUDzb3oe8RBQ4tYGqsuPsCbsVE4KWfktXRihXf8Ggq": [
20964,
347474,
347475
],
"12ashmTiFStQ8RGUpi1BTCinJakVyDKWjRL6SWhnbxbT": [
1992,
1993,
109096
]
}
To try first I have tried to insert only first element's key and value. It is working correctly.
std::vector<std::pair<std::string, std::vector<uint64_t>>> vec;
auto key = j.begin().key();
auto value = j.begin().value();
vec.push_back(std::make_pair(key, value));
Now I am trying this way to insert all the key values in vector
std::vector<std::pair<std::string, std::vector<uint64_t>>> vec;
int i = 0;
while ((j.begin() + i) != j.end()) {
auto key = (j.begin() + i).key();
auto value = (j.begin() + i).value();
vec.push_back(std::make_pair(key, value));
i++;
}
I am getting the error:
[json.exception.invalid_iterator.209]
cannot use offsets with object iterators
Can someone please what is the correct way of doing this ?
I think you're over complicating this. You can iterate over a json object the same way you would any other container using a for loop:
#include "nlohmann/json.hpp"
#include <iostream>
int main()
{
nlohmann::json j = nlohmann::json::parse(R"({
"12CUDzb3oe8RBQ4tYGqsuPsCbsVE4KWfktXRihXf8Ggq": [
20964,
347474,
347475
],
"12ashmTiFStQ8RGUpi1BTCinJakVyDKWjRL6SWhnbxbT": [
1992,
1993,
109096
]
})");
std::vector<std::pair<std::string, std::vector<uint64_t>>> vec;
for (auto it = j.begin(); it != j.end(); ++it)
{
vec.emplace_back(it.key(), it.value());
}
for (auto& it : vec)
{
std::cout << it.first << ": ";
for (auto& value : it.second)
{
std::cout << value << ", ";
}
std::cout << "\n";
}
}
If you don't care about the order of items (JSON keys are unordered anyway and nlohmann doesn't preserve the order by default) then you can do this in a one liner:
std::map<std::string, std::vector<uint64_t>> vec = j;

What is the correct way to move key-value contents from the first map to the second(value-key) map?

I am trying to count the most common words in text, so firstly i am filling the map of <word, count>, secondly I am trying to move the contents of the map <word, count> to the multimap<count, word>. But there is the problem: node type of STL maps/hashes are <const Key, Value>. I have tried to const_cast, it worked, but it seems ugly and UB'ly. Are there any another ways to move the contents?
int main()
{
std::unordered_map<std::string, std::size_t> words;
std::multimap<std::size_t, std::string> sorted_ordered_words;
std::for_each(std::istream_iterator<std::string>{std::cin}, {}, [&](const std::string& str)
{
++words[str];
});
std::transform(std::make_move_iterator(std::begin(words)),
std::make_move_iterator(std::end(words)),
std::inserter(sorted_ordered_words, std::end(sorted_ordered_words)),
[](decltype (words)::value_type && v)
{
return std::make_pair(v.second, std::move(const_cast<std::string&>(v.first)));
});
for (const auto &[count, word] : sorted_ordered_words)
{
std::cout << count << " - " << word << std::endl;
}
return 0;
}
A well defined way to move the string from the original container is to extract the node it is in first. That exposes a non-const reference to the key.
for (auto it = words.begin(); it != words.end();) {
auto copy = it;
++it;
auto node = words.extract(copy);
static_assert(std::is_same_v<decltype(std::move(node.key())), std::string&&>);
sorted_ordered_words.emplace(node.mapped(), std::move(node.key()));
}
Using const_cast is definitely UB because the keys are just moved from but are still in the map, violating internal constraints (all strings are the same, "", when they should all be unique). This could lead to crashes in the destructor for example.
If it's just for printing in order, you don't need to build a multimap. It's faster to just sort a vector of pairs rather than inserting into a multimap one by one.
std::vector<decltype(words)::value_type*> vec;
vec.reserve(words.size());
std::transform(words.begin(), words.end(), std::back_inserter(vec), [](auto& v) { return &v; });
std::sort(vec.begin(), vec.end(), [](auto* l, auto* r) {
return l->second < r->second;
});
for (const auto* item : vec) {
const auto& [word, count] = *item;
std::cout << count << " - " << word << std::endl;
}
You could also use std::vector<std::pair<std::size_t, const std::string&>> and sort by .first.

Can I use std::find() with lambda function in vc++ [VS 2019]?

OS: Windows 10 Pro 64-bit(10.0, build 18362)
IDE: Visual Studio 2019(Version 16.4.3) [Before this I was using VS 2010]
Language: c++ and VC++
Here's my simple code in which i just want to find element 3 and print it if found
std::vector<int> intVect;
for (int counter = 0; counter < 5; counter++)
{
intVect.push_back(counter);
}
std::find(intVect.begin(), intVect.end(), [](int a)
{
if (a == 3)
{
std::cout << "Item Found.." << std::endl;
}
});
The probelm is when i compile this code it's giving me error like below:
Error C2678 binary '==' : no operator found which takes a left-hand
operand of type 'int' (or there is no acceptable conversion)
c:\Program Files (x86)\Microsoft Visual Studio
10.0\VC\include\algorithm 41
You can do this easily without a lambda:
auto val = std::find(intVect.begin(), intVect.end(), 3);
if (val != intVect.end()) {
std::cout << "Value found\n";
}
However, it's also possible to use a lambda, but you need to use std::find_if (available in the same header as std::find: <algorithm>):
auto val = std::find_if(intVect.begin(), intVect.end(), [](int i) { return i == 3; });
if (val != intVect.end()) {
std::cout << "Value found\n";
}
But there's really no point. You'd only use find_if in this case if you had something more complex, like a struct and you were searching for a particular member:
struct complex {
int id;
...
};
std::vector<complex> things = ...;
auto val = std::find_if(things.begin(), things.end(), [](const complex& c) { return c.id == 3; });
if (val != things.end()) {
std::cout << "Value found\n";
}
std::find expects a value to be compared to elements, so it should be
if (std::find(intVect.begin(), intVect.end(), 3) != intVect.end())
{
std::cout << "Item Found.." << std::endl;
}
If you want to use a lambda as predicate, you should use std::find_if; note that the lambda should return bool.
if (std::find_if(intVect.begin(), intVect.end(), [](int a) { return (a == 3); }) != intVect.end())
{
std::cout << "Item Found.." << std::endl;
}

How to implement auto iterators correctly

I decided to replace
for (auto messageIterator = message.begin(); messageIterator != message.end(); ++messageIterator)
with
for (auto &messageIterator : message)
and it works. Then I decided to apply similar approach to this loop
for (auto alphabetIterator = alphabet.begin(), rotorIterator = rotor.begin(); alphabetIterator != alphabet.end(), rotorIterator != rotor.end(); ++alphabetIterator, ++rotorIterator)
and my code looks like this but it doesn't work.
for (auto &alphabetIterator : alphabet, &rotorIterator : rotor)
How do I fix it?
With range-v3, you might do:
std::vector<int> vi{1, 2, 3};
std::vector<std::string> vs{"one", "two", "three"};
for (const auto& [i, s] : ranges::view::zip(vi, vs)) {
std::cout << i << " " << s << std::endl;
}
Demo

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.