Find a substring from a vector using iterators - c++

I am trying to make a search function in my application. If the user inputs a substring (or the complete string) I want to know if that substring matches any of the strings or part of the strings stored in my vector.
The following code is written so far:
cout << "Input word to search for: ";
cin >> searchString;
for (multimap <string, vector<string> >::const_iterator it = contactInformationMultimap.cbegin(); it != contactInformationMultimap.cend(); ++it)
{
for (vector<string>::const_iterator iter = it->second.cbegin(); iter != it->second.cend(); ++iter)
{
if (*iter.find(searchString))
^^^^^^^^^^^^^^^^^^^^^^^ this does not work, if i cout *iter it is the correct word stored in the vector. The problem is that i can not use the find function.
}
}
Anyone having any suggestions?

Unary operators have less priority than postfix operators. In your if statement you need that the unary operator * would be evaluated before member access operator. So you have to write
if ( ( *iter ).find(searchString) != std::string::npos )
Or you could write
if ( iter->find(searchString) != std::string::npos )
Take into account that this record
if ( ( *iter ).find(searchString) )
makes no sense.
Also you could write
for (multimap <string, vector<string> >::const_iterator it = contactInformationMultimap.cbegin(); it != contactInformationMultimap.cend(); ++it)
{
for ( const std::string &s : it->second )
{
if ( s.find(searchString ) != std::string::npos ) /*...*/;
}
}

The comments have shown how to correct the syntax so your code can compile, but the result is code that I'd still (at least personally) rather avoid. The primary reason for iterators to allow their use in generic algorithms. In this case, generic algorithms can do the job quite nicely. For example, let's assume that you wanted to print out the key for every record that the value associated with that key contained whatever value was in searchString. To do that you could write the code like this:
std::copy_if(data.begin(), data.end(), // The source "range"
std::ostream_iterator<T>(std::cout, "\n"), // the destination "range"
[&](T const &v) {
return std::any_of(v.second.begin(), v.second.end(),
[&](std::string const &s) {
return s.find(searchString) != std::string::npos;
}
);
}
);
This depends on an operator<< for the correct type, something like this:
typedef std::pair < std::string, std::vector<std::string>> T;
namespace std {
std::ostream &operator<<(std::ostream &os, T const &t) {
return os << t.first;
}
}
A complete test program could look like this:
#include <map>
#include <algorithm>
#include <iostream>
#include <iterator>
#include <vector>
#include <string>
typedef std::pair < std::string, std::vector<std::string>> T;
namespace std {
std::ostream &operator<<(std::ostream &os, T const &t) {
return os << t.first;
}
}
int main() {
std::multimap<std::string, std::vector<std::string>> data{
{ "key1", { "Able", "Bend", "Cell" } },
{ "key2", { "Are", "Dead" } },
{ "key3", { "Bad", "Call" } }
};
std::string searchString = "a";
std::copy_if(data.begin(), data.end(),
std::ostream_iterator<T>(std::cout, "\n"),
[&](T const &v) {
return std::any_of(v.second.begin(), v.second.end(),
[&](std::string const &s) {
return s.find(searchString) != std::string::npos;
}
);
}
);
}
Result:
key2
key3

You can use : if ( *iter.find(searchString) != string::npos )

Related

A better approach for keyword searching in c++

Can you help me with this aproach :
The thing is, we need to do a case-insensitive search for the keywords in a string (for a function which return true is if any of the keyword is found in the string, elsewise false)
So I am using this piece of code:
std::transform(string.begin(), string.end(), string.begin(), ::toupper);
std::transform(keywords.begin(), keywords.end(), keywords.begin(), ::toupper);
std::istringstream iss(keywords);
std::string word;
while(iss >> word) {
if(string.find(word) != std::string::npos)
return true;
}
return false;
The problem with this is that it creates unnecessary copies of the existing data. Can there be a better approach to it?
First of all for making this more reuseable creating an object responsible for holding the keyword data would be preferrable. You can use std::string_views, std::pair<std::string::const_iterator, std::string::const_iterator> or something similar to avoid making a copy of the string data for the keywords and using std::search to find the keywords allows you to prevent having to copy the string to convert it to upper case for a search while also keeping benefit of converting the keywords to upper case:
class KeywordSearch
{
std::vector<std::string_view> m_keywords;
std::string m_keywordData;
public:
KeywordSearch(std::string&& keywords)
: m_keywordData(std::move(keywords))
{
auto pos = m_keywordData.begin();
auto const end = m_keywordData.end();
std::for_each(pos, end, [](char& c) { c = std::toupper(c); });
pos = std::find_if(pos, end, [](unsigned char c) { return !std::isspace(c); });
while (pos != end)
{
auto keywordEnd = std::find_if(pos + 1, end, [](unsigned char c) { return std::isspace(c); });
m_keywords.emplace_back(pos, keywordEnd);
pos = std::find_if(keywordEnd, end, [](unsigned char c) { return !std::isspace(c); });
}
}
// allow only move for now; copy would require an update of m_keywords
KeywordSearch(KeywordSearch&&) noexcept = default;
KeywordSearch& operator=(KeywordSearch&&) noexcept = default;
bool operator()(std::string const& haysack) const
{
for (auto const& keyword : m_keywords)
{
if (std::search(haysack.begin(), haysack.end(), keyword.begin(), keyword.end(),
[](char haysackChar, char keywordChar)
{
return std::toupper(haysackChar) == keywordChar;
}) != haysack.end())
{
return true;
}
}
return false;
}
};
void Test(KeywordSearch const& search, std::string const& str)
{
std::cout << (search(str) ? " keyword found in '" : "keyword not found in '") << str << "'\n";
}
int main() {
KeywordSearch search("foo bar baz");
Test(search, "NoFoOB");
Test(search, "barblabla");
Test(search, "babbba");
Test(search, "hello world");
Test(search, "hello wobaz");
}
Yes, maybe. If you want to avoid copies, then you can use an iterator.
C++ offers a functionality to iterate over paterrns in a string. This is the std::sregex_token_iterator. You can read here about that. You can either define a "positive" pattern, so, what you are looking for. Example: "\w+" will look for words. Or, you do a "negative" search, meaning, specify the separator (e.g., ' ' as a std::regex) and add "-1" as fourth parameter.
Then you can iterate over all keywords.
As for the case insenitivity. Do the conversion one time. I will not even show it in my below example.
First I created a small demo, where I print out the keywords that have been found in the given std::string.
#include <iostream>
#include <regex>
#include <string>
#include <iterator>
#include <algorithm>
const std::regex re{ R"(\w+)" };
int main() {
// Keys
const std::string keys{ "abc def ghi jkl" };
// Search string
std::string s{ "abcxxxghixxx" };
std::copy_if(std::sregex_token_iterator(keys.begin(), keys.end(), re), {},
std::ostream_iterator<std::string>(std::cout,"\n"),
[&s](const std::string& k) {return s.find(k) != std::string::npos; });
}
This approach can be taken over to build your needed function.
One of many possible solutions:
#include <iostream>
#include <regex>
#include <string>
#include <iterator>
#include <algorithm>
const std::regex re{ R"(\w+)" };
bool isAnyKeyWordInString(const std::string& keys, const std::string& s) {
bool result{};
std::for_each(std::sregex_token_iterator(keys.begin(), keys.end(), re), {},
[&](const std::string& k) {result |= (s.find(k) != std::string::npos); });
return result;
}
int main() {
// Keys
const std::string keys{ "abc def ghi jkl" };
// Search string
std::string s{ "abcxxxghixxx" };
// Evaluate
if (isAnyKeyWordInString(keys, s))
std::cout << "At least one key-word found\n";
else
std::cout << "No Keyword found\n";
}
this is fast!!!
but you have to keep changing the parameters depending on your requirement, for example: const int keyword_len = (int)keyword.length(); here i'm casting the unsigned long int to int.
also i'm not converting my searchable string to upper case before my conditional statement because its faster this way instead of looping through the string when i need to search.
when it comes to complexity it's O(n)+k where k<<n, because i'm not comparing the entire keyword size to the searchable string.
P.s. sorry for not making this code ideally reusable, but it is intractable.
#include <cctype>
#include <iostream>
#include <string>
int main(){
std::string searchable_string;
std::string keyword;
std::cout<<"enter the searchable string : "<<std::endl;
std::getline(std::cin, searchable_string);
std::cout<<"enter the keyword you are looking for : "<<std::endl;
std::getline(std::cin, keyword);
const int keyword_len = (int)keyword.length();
const int searchable_string_len = (int)searchable_string.length();
int upper_keyword[keyword_len];
int i = 0;
for(;i<keyword_len;){
upper_keyword[i] = toupper(x);
i++;
}
i = 0;
int j;
keeplooking:
j = 0;
comparetokeyword:
if(upper_keyword[j]==toupper(searchable_string[i+j])){
j++;
if(j <= (keyword_len)-1) goto comparetokeyword;
else {
//'i' is the relative position of your keyword.
std::cout<<"found keyword at: "<<std::endl<<i<<std::endl;
std::cout<<"do you want to keep looking for more(enter '1' for Yes and '0' for No) :"<<std::endl;
bool x;
std::cin>>x;
if(x){
i++;
if(i<searchable_string_len) goto keeplooking;
else goto terminate;
}
}
}
else{
i++;
if(i<searchable_string_len){
goto keeplooking;
}
terminate:
std::cout<<"reached the end."<<std::endl;
}
}

Could you recommend, how reimplement split function to work with string_view?

I write this split function, can't find easy way to split by string_view(several chars).
My function:
size_t split(std::vector<std::string_view>& result, std::string_view in, char sep) {
result.reserve(std::count(in.begin(), in.end(), in.find(sep) != std::string::npos) + 1);
for (auto pfirst = in.begin();; ++pfirst) {
auto pbefore = pfirst;
pfirst = std::find(pfirst, in.end(), sep);
result.emplace_back(q, pfirst-pbefore);
if (pfirst == in.end())
return result.size();
}
}
I want to call this split function with string_view separator. For example:
str = "apple, phone, bread\n keyboard, computer"
split(result, str, "\n,")
Result:['apple', 'phone', 'bread', 'keyboard', 'computer']
My question is, how can i implement this function as fast as possible?
First, you are using std::count() incorrectly.
Second, std::string_view has its own find_first_of() and substr() methods, which you can use in this situation, instead of using iterators. find_first_of() allows you to specify multiple characters to search for.
Try something more like this:
size_t split(std::vector<std::string_view>& result, std::string_view in, std::string_view seps) {
result.reserve(std::count_if(in.begin(), in.end(), [&](char ch){ return seps.find(ch) != std::string_view::npos; }) + 1);
std::string_view::size_type start = 0, end;
while ((end = in.find_first_of(seps, start)) != std::string_view::npos) {
result.push_back(in.substr(start, end-start));
start = in.find_first_not_of(' ', end+1);
}
if (start != std::string_view::npos)
result.push_back(in.substr(start));
return result.size();
}
Online Demo
This is my take on splitting a string view, just loops once over all the characters in the string view and returns a vector of string_views (so no copying of data)
The calling code can still use words.size() to get the size if needed.
(I use C++20 std::set contains function)
Live demo here : https://onlinegdb.com/tHfPIeo1iM
#include <iostream>
#include <set>
#include <string_view>
#include <vector>
auto split(const std::string_view& string, const std::set<char>& separators)
{
std::vector<std::string_view> words;
auto word_begin{ string.data() };
std::size_t word_len{ 0ul };
for (const auto& c : string)
{
if (!separators.contains(c))
{
word_len++;
}
else
{
// we found a word and not a seperator repeat
if (word_len > 0)
{
words.emplace_back(word_begin, word_len);
word_begin += word_len;
word_len = 0;
}
word_begin++;
}
}
// string_view doesn't have a trailing zero so
// also no trailing separator so if there is still
// a word in the "pipeline" add it too
if (word_len > 0)
{
words.emplace_back(word_begin, word_len);
}
return words;
}
int main()
{
std::set<char> seperators{ ' ', ',', '.', '!', '\n' };
auto words = split("apple, phone, bread\n keyboard, computer", seperators);
bool comma = false;
std::cout << "[";
for (const auto& word : words)
{
if (comma) std::cout << ", ";
std::cout << word;
comma = true;
}
std::cout << "]\n";
return 0;
}
I do not know about performance, but this code seems a lot simpler
std::vector<std::string> ParseDelimited(
const std::string &l, char delim )
{
std::vector<std::string> token;
std::stringstream sst(l);
std::string a;
while (getline(sst, a, delim))
token.push_back(a);
return token;
}

Obtaining First and Last Charsof Each String From A Vector of Strings

I have created a vector<string> names; which stores peoples first names. I want to take each name and create two variables first_letter and last_letter which contains the first and last characters of each name. However I am not quite sure how to get this done since I am just starting with c++. Can anyone explain to me how this can be done, and possibly provide an example?
In C++11 it got easier:
for (std::string& name : names) {
char first_letter = name.front();
char last_letter = name.back();
// do stuff
}
Before that, you'd have to access them directly using operator[]:
for (size_t i = 0; i < names.size(); ++i) {
std::string& name = names[i];
char first_letter = name[0];
char last_letter = name[name.size() - 1];
// do stuff
}
Assuming name is your string and you're OK with using C++11, name.front() and name.back() will work, otherwise dereference the iterator: *name.begin() and *name.rbegin(). Though you'd check whether the name is empty or not:
if (!name.empty()) {
// Safe to proceed now
}
You can iterate over names like (range loop - since C++11)
for (auto& name : names) {
// Do things with individual name
}
or (for older C++)
for (vector<string>::iterator it = names.begin(); it != names.end(); it++) {
// Do things with individual name (*it)
}
It's advised to use constant iterators where possible, if you're not planning to modify strings, replace auto with const auto and ::iterator with ::const_iterator.
Use the string functions front() and back().
Make sure that the string is not empty before using these functions:
Assuming that i is an index into your vector:
if ( !names[i].empty() )
{
char fChar = names[i].front();
char bChar = names[i].back();
}
Create a function to get the two letters from a single string:
std::pair<char, char>
first_and_last(const std::string& s)
{
if (s.length() == 0)
throw std::runtime_error("Empty string!")
return {s.front(), s.back()};
}
(for C++03 return std::make_pair(s[0], s[s.length()-1]) or another of the ways to do it shown by the other answers.)
Then apply that function to each name in turn, saving the results in a new vector:
std::vector<std::pair<char, char>> letters;
letters.reserve(names.size());
std::transform(names.begin(), names.end(), std::back_inserter(letters), first_and_last);
Or use the C++11 range-based for loop:
std::vector<std::pair<char, char>> letters;
letters.reserve(names.size());
for (const auto& name : names)
letters.push_back( first_and_last(name) );
Something like this? There's no error checking, but it's a start.
vector<string> names = ...;
for (vector<string>::iterator i = names.begin(); i != names.end(); ++i)
{
string first_letter = i->substr(0, 1);
string last_letter = i->substr(i->size() - 1, 1);
}
First off, of course, you start with a loop to iterate through the vector.
Then you get those characters with substr, it would look something like this
vector <string>::iterator it;
for(it = names.begin(); it != names.end(); it++)
{
string first = (*it).substr(0, 1);
string second = (*it).substr((*it).length()-1, 1);
..
do whatever you want to
..
}
Consider the following approach
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <utility>
#include <iterator>
int main()
{
std::vector<std::string> v { "Hello", "NinjaZ", "How", "do", "you", "do" };
for ( const auto &s : v ) std::cout << s << ' ';
std::cout << std::endl;
std::vector<std::pair<char, char>> v2;
v2.reserve( v.size() );
std::transform( v.begin(), v.end(),
std::back_inserter( v2 ),
[]( const std::string &s )
{
return std::make_pair( s.front(), s.back() );
} );
for ( const auto &p : v2 )
{
std::cout << p.first << ' ' << p.second << std::endl;
}
return 0;
}
The output is
Hello NinjaZ How do you do
H o
N Z
H w
d o
y u
d o
Instead of the algorithm std::transform you could use an ordinary range based for loop. For example
for ( const auto &s : v ) v2.push_back( { s.front(), s.back() } );
There are many ways to skin this cat.
Here's another short readable example using C++11. What this brings to the table is the use of std::vector::emplace_back which allows for in-place construction of elements, as opposed to move- or copyconstructing. Also shorter syntax which is nice.
Say you have a container that stores the pairs of letters.
std::vector<std::pair<char, char>> letters;
Then use this:
for (auto&& name : names)
letters.emplace_back(name.front(), name.back());
If you want to throw on empty name strings, simply add a statement before the std::vector::emplace_back statement:
if (name.empty()) throw std::runtime_error("Empty string!");

Getting the words from a sentence and storing them in a vector of strings

Alright, guys ...
Here's my set that has all the letters. I'm defining a word as consisting of consecutive letters from the set.
const char LETTERS_ARR[] = {"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"};
const std::set<char> LETTERS_SET(LETTERS_ARR, LETTERS_ARR + sizeof(LETTERS_ARR)/sizeof(char));
I was hoping that this function would take in a string representing a sentence and return a vector of strings that are the individual words in the sentence.
std::vector<std::string> get_sntnc_wrds(std::string S) {
std::vector<std::string> retvec;
std::string::iterator it = S.begin();
while (it != S.end()) {
if (LETTERS_SET.count(*it) == 1) {
std::string str(1,*it);
int k(0);
while (((it+k+1) != S.end()) && (LETTERS_SET.count(*(it+k+1) == 1))) {
str.push_back(*(it + (++k)));
}
retvec.push_back(str);
it += k;
}
else {
++it;
}
}
return retvec;
}
For instance, the following call should return a vector of the strings "Yo", "dawg", etc.
std::string mystring("Yo, dawg, I heard you life functions, so we put a function inside your function so you can derive while you derive.");
std::vector<std::string> mystringvec = get_sntnc_wrds(mystring);
But everything isn't going as planned. I tried running my code and it was putting the entire sentence into the first and only element of the vector. My function is very messy code and perhaps you can help me come up with a simpler version. I don't expect you to be able to trace my thought process in my pitiful attempt at writing that function.
Try this instead:
#include <vector>
#include <cctype>
#include <string>
#include <algorithm>
// true if the argument is whitespace, false otherwise
bool space(char c)
{
return isspace(c);
}
// false if the argument is whitespace, true otherwise
bool not_space(char c)
{
return !isspace(c);
}
vector<string> split(const string& str)
{
typedef string::const_iterator iter;
vector<string> ret;
iter i = str.begin();
while (i != str.end())
{
// ignore leading blanks
i = find_if(i, str.end(), not_space);
// find end of next word
iter j = find_if(i, str.end(), space);
// copy the characters in [i, j)
if (i != str.end())
ret.push_back(string(i, j));
i = j;
}
return ret;
}
The split function will return a vector of strings, each element containing one word.
This code is taken from the Accelerated C++ book, so it's not mine, but it works. There are other superb examples of using containers and algorithms for solving every-day problems in this book. I could even get a one-liner to show the contents of a file at the output console. Highly recommended.
It's just a bracketing issue, my advice is (almost) never put in more brackets than are necessary, it's only confuses things
while (it+k+1 != S.end() && LETTERS_SET.count(*(it+k+1)) == 1) {
Your code compares the character with 1 not the return value of count.
Also although count does return an integer in this context I would simplify further and treat the return as a boolean
while (it+k+1 != S.end() && LETTERS_SET.count(*(it+k+1))) {
You should use the string steam with std::copy like so:
#include <iostream>
#include <string>
#include <sstream>
#include <algorithm>
#include <iterator>
#include <vector>
int main() {
std::string sentence = "And I feel fine...";
std::istringstream iss(sentence);
std::vector<std::string> split;
std::copy(std::istream_iterator<std::string>(iss),
std::istream_iterator<std::string>(),
std::back_inserter(split));
// This is to print the vector
for(auto iter = split.begin();
iter != split.end();
++iter)
{
std::cout << *iter << "\n";
}
}
I would use another more simple approach based on member functions of class std::string. For example
const char LETTERS[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
std::string s( "This12 34is 56a78 test." );
std::vector<std::string> v;
for ( std::string::size_type first = s.find_first_of( LETTERS, 0 );
first != std::string::npos;
first = s.find_first_of( LETTERS, first ) )
{
std::string::size_type last = s.find_first_not_of( LETTERS, first );
v.push_back(
std::string( s, first, last == std::string::npos ? std::string::npos : last - first ) );
first = last;
}
for ( const std::string &s : v ) std::cout << s << ' ';
std::cout << std::endl;
Here you make 2 mistakes, I have correct in the following code.
First, it should be
while (((it+k+1) != S.end()) && (LETTERS_SET.count(*(it+k+1)) == 1))
and, it should move to next by
it += (k+1);
and the code is
std::vector<std::string> get_sntnc_wrds(std::string S) {
std::vector<std::string> retvec;
std::string::iterator it = S.begin();
while (it != S.end()) {
if (LETTERS_SET.count(*it) == 1) {
std::string str(1,*it);
int k(0);
while (((it+k+1) != S.end()) && (LETTERS_SET.count(*(it+k+1)) == 1)) {
str.push_back(*(it + (++k)));
}
retvec.push_back(str);
it += (k+1);
}
else {
++it;
}
}
return retvec;
}
The output have been tested.

How to implode a vector of strings into a string (the elegant way)

I'm looking for the most elegant way to implode a vector of strings into a string. Below is the solution I'm using now:
static std::string& implode(const std::vector<std::string>& elems, char delim, std::string& s)
{
for (std::vector<std::string>::const_iterator ii = elems.begin(); ii != elems.end(); ++ii)
{
s += (*ii);
if ( ii + 1 != elems.end() ) {
s += delim;
}
}
return s;
}
static std::string implode(const std::vector<std::string>& elems, char delim)
{
std::string s;
return implode(elems, delim, s);
}
Is there any others out there?
Use boost::algorithm::join(..):
#include <boost/algorithm/string/join.hpp>
...
std::string joinedString = boost::algorithm::join(elems, delim);
See also this question.
std::vector<std::string> strings;
const char* const delim = ", ";
std::ostringstream imploded;
std::copy(strings.begin(), strings.end(),
std::ostream_iterator<std::string>(imploded, delim));
(include <string>, <vector>, <sstream> and <iterator>)
If you want to have a clean end (no trailing delimiter) have a look here
You should use std::ostringstream rather than std::string to build the output (then you can call its str() method at the end to get a string, so your interface need not change, only the temporary s).
From there, you could change to using std::ostream_iterator, like so:
copy(elems.begin(), elems.end(), ostream_iterator<string>(s, delim));
But this has two problems:
delim now needs to be a const char*, rather than a single char. No big deal.
std::ostream_iterator writes the delimiter after every single element, including the last. So you'd either need to erase the last one at the end, or write your own version of the iterator which doesn't have this annoyance. It'd be worth doing the latter if you have a lot of code that needs things like this; otherwise the whole mess might be best avoided (i.e. use ostringstream but not ostream_iterator).
Because I love one-liners (they are very useful for all kinds of weird stuff, as you'll see at the end), here's a solution using std::accumulate and C++11 lambda:
std::accumulate(alist.begin(), alist.end(), std::string(),
[](const std::string& a, const std::string& b) -> std::string {
return a + (a.length() > 0 ? "," : "") + b;
} )
I find this syntax useful with stream operator, where I don't want to have all kinds of weird logic out of scope from the stream operation, just to do a simple string join. Consider for example this return statement from method that formats a string using stream operators (using std;):
return (dynamic_cast<ostringstream&>(ostringstream()
<< "List content: " << endl
<< std::accumulate(alist.begin(), alist.end(), std::string(),
[](const std::string& a, const std::string& b) -> std::string {
return a + (a.length() > 0 ? "," : "") + b;
} ) << endl
<< "Maybe some more stuff" << endl
)).str();
Update:
As pointed out by #plexando in the comments, the above code suffers from misbehavior when the array starts with empty strings due to the fact that the check for "first run" is missing previous runs that have resulted in no additional characters, and also - it is weird to run a check for "is first run" on all runs (i.e. the code is under-optimized).
The solution for both of these problems is easy if we know for a fact that the list has at least one element. OTOH, if we know for a fact that the list does not have at least one element, then we can shorten the run even more.
I think the resulting code isn't as pretty, so I'm adding it here as The Correct Solution, but I think the discussion above still has merrit:
alist.empty() ? "" : /* leave early if there are no items in the list */
std::accumulate( /* otherwise, accumulate */
++alist.begin(), alist.end(), /* the range 2nd to after-last */
*alist.begin(), /* and start accumulating with the first item */
[](auto& a, auto& b) { return a + "," + b; });
Notes:
For containers that support direct access to the first element, its probably better to use that for the third argument instead, so alist[0] for vectors.
As per the discussion in the comments and chat, the lambda still does some copying. This can be minimized by using this (less pretty) lambda instead: [](auto&& a, auto&& b) -> auto& { a += ','; a += b; return a; }) which (on GCC 10) improves performance by more than x10. Thanks to #Deduplicator for the suggestion. I'm still trying to figure out what is going on here.
I like to use this one-liner accumulate (no trailing delimiter):
(std::accumulate defined in <numeric>)
std::accumulate(
std::next(elems.begin()),
elems.end(),
elems[0],
[](std::string a, std::string b) {
return a + delimiter + b;
}
);
what about simple stupid solution?
std::string String::join(const std::vector<std::string> &lst, const std::string &delim)
{
std::string ret;
for(const auto &s : lst) {
if(!ret.empty())
ret += delim;
ret += s;
}
return ret;
}
With fmt you can do.
#include <fmt/format.h>
auto s = fmt::format("{}",fmt::join(elems,delim));
But I don't know if join will make it to std::format.
string join(const vector<string>& vec, const char* delim)
{
stringstream res;
copy(vec.begin(), vec.end(), ostream_iterator<string>(res, delim));
return res.str();
}
Especially with bigger collections, you want to avoid having to check if youre still adding the first element or not to ensure no trailing separator...
So for the empty or single-element list, there is no iteration at all.
Empty ranges are trivial: return "".
Single element or multi-element can be handled perfectly by accumulate:
auto join = [](const auto &&range, const auto separator) {
if (range.empty()) return std::string();
return std::accumulate(
next(begin(range)), // there is at least 1 element, so OK.
end(range),
range[0], // the initial value
[&separator](auto result, const auto &value) {
return result + separator + value;
});
};
Running sample (require C++14): http://cpp.sh/8uspd
A version that uses std::accumulate:
#include <numeric>
#include <iostream>
#include <string>
struct infix {
std::string sep;
infix(const std::string& sep) : sep(sep) {}
std::string operator()(const std::string& lhs, const std::string& rhs) {
std::string rz(lhs);
if(!lhs.empty() && !rhs.empty())
rz += sep;
rz += rhs;
return rz;
}
};
int main() {
std::string a[] = { "Hello", "World", "is", "a", "program" };
std::string sum = std::accumulate(a, a+5, std::string(), infix(", "));
std::cout << sum << "\n";
}
While I would normally recommend using Boost as per the top answer, I recognise that in some projects that's not desired.
The STL solutions suggested using std::ostream_iterator will not work as intended - it'll append a delimiter at the end.
There is now a way to do this with modern C++ using std::experimental::ostream_joiner:
std::ostringstream outstream;
std::copy(strings.begin(),
strings.end(),
std::experimental::make_ostream_joiner(outstream, delimiter.c_str()));
return outstream.str();
Here's what I use, simple and flexible
string joinList(vector<string> arr, string delimiter)
{
if (arr.empty()) return "";
string str;
for (auto i : arr)
str += i + delimiter;
str = str.substr(0, str.size() - delimiter.size());
return str;
}
using:
string a = joinList({ "a", "bbb", "c" }, "!##");
output:
a!##bbb!##c
Here is another one that doesn't add the delimiter after the last element:
std::string concat_strings(const std::vector<std::string> &elements,
const std::string &separator)
{
if (!elements.empty())
{
std::stringstream ss;
auto it = elements.cbegin();
while (true)
{
ss << *it++;
if (it != elements.cend())
ss << separator;
else
return ss.str();
}
}
return "";
Using part of this answer to another question gives you a joined this, based on a separator without a trailing comma,
Usage:
std::vector<std::string> input_str = std::vector<std::string>({"a", "b", "c"});
std::string result = string_join(input_str, ",");
printf("%s", result.c_str());
/// a,b,c
Code:
std::string string_join(const std::vector<std::string>& elements, const char* const separator)
{
switch (elements.size())
{
case 0:
return "";
case 1:
return elements[0];
default:
std::ostringstream os;
std::copy(elements.begin(), elements.end() - 1, std::ostream_iterator<std::string>(os, separator));
os << *elements.rbegin();
return os.str();
}
}
Another simple and good solution is using ranges v3. The current version is C++14 or greater, but there are older versions that are C++11 or greater. Unfortunately, C++20 ranges don't have the intersperse function.
The benefits of this approach are:
Elegant
Easily handle empty strings
Handles the last element of the list
Efficiency. Because ranges are lazily evaluated.
Small and useful library
Functions breakdown(Reference):
accumulate = Similar to std::accumulate but arguments are a range and the initial value. There is an optional third argument that is the operator function.
filter = Like std::filter, filter the elements that don't fit the predicate.
intersperse = The key function! Intersperses a delimiter between range input elements.
#include <iostream>
#include <string>
#include <vector>
#include <range/v3/numeric/accumulate.hpp>
#include <range/v3/view/filter.hpp>
#include <range/v3/view/intersperse.hpp>
int main()
{
using namespace ranges;
// Can be any std container
std::vector<std::string> a{ "Hello", "", "World", "is", "", "a", "program" };
std::string delimiter{", "};
std::string finalString =
accumulate(a | views::filter([](std::string s){return !s.empty();})
| views::intersperse(delimiter)
, std::string());
std::cout << finalString << std::endl; // Hello, World, is, a, program
}
A possible solution with ternary operator ?:.
std::string join(const std::vector<std::string> & v, const std::string & delimiter = ", ") {
std::string result;
for (size_t i = 0; i < v.size(); ++i) {
result += (i ? delimiter : "") + v[i];
}
return result;
}
join({"2", "4", "5"}) will give you 2, 4, 5.
If you are already using a C++ base library (for commonly used tools), string-processing features are typically included. Besides Boost mentioned above, Abseil provides:
std::vector<std::string> names {"Linus", "Dennis", "Ken"};
std::cout << absl::StrJoin(names, ", ") << std::endl;
Folly provides:
std::vector<std::string> names {"Linus", "Dennis", "Ken"};
std::cout << folly::join(", ", names) << std::endl;
Both give the string "Linus, Dennis, Ken".
Slightly long solution, but doesn't use std::ostringstream, and doesn't require a hack to remove the last delimiter.
http://www.ideone.com/hW1M9
And the code:
struct appender
{
appender(char d, std::string& sd, int ic) : delim(d), dest(sd), count(ic)
{
dest.reserve(2048);
}
void operator()(std::string const& copy)
{
dest.append(copy);
if (--count)
dest.append(1, delim);
}
char delim;
mutable std::string& dest;
mutable int count;
};
void implode(const std::vector<std::string>& elems, char delim, std::string& s)
{
std::for_each(elems.begin(), elems.end(), appender(delim, s, elems.size()));
}
This can be solved using boost
#include <boost/range/adaptor/filtered.hpp>
#include <boost/algorithm/string/join.hpp>
#include <boost/algorithm/algorithm.hpp>
std::vector<std::string> win {"Stack", "", "Overflow"};
const std::string Delimitor{","};
const std::string combined_string =
boost::algorithm::join(win |
boost::adaptors::filtered([](const auto &x) {
return x.size() != 0;
}), Delimitor);
Output:
combined_string: "Stack,Overflow"
I'm using the following approach that works fine in C++17. The function starts checking if the given vector is empty, in which case returns an empty string. If that's not the case, it takes the first element from the vector, then iterates from the second one until the end and appends the separator followed by the vector element.
template <typename T>
std::basic_string<T> Join(std::vector<std::basic_string<T>> vValues,
std::basic_string<T> strDelim)
{
std::basic_string<T> strRet;
typename std::vector<std::basic_string<T>>::iterator it(vValues.begin());
if (it != vValues.end()) // The vector is not empty
{
strRet = *it;
while (++it != vValues.end()) strRet += strDelim + *it;
}
return strRet;
}
Usage example:
std::vector<std::string> v1;
std::vector<std::string> v2 { "Hello" };
std::vector<std::string> v3 { "Str1", "Str2" };
std::cout << "(1): " << Join<char>(v1, ",") << std::endl;
std::cout << "(2): " << Join<char>(v2, "; ") << std::endl;
std::cout << "(3): [" << Join<char>(v3, "] [") << "]" << std::endl;
Output:
(1):
(2): Hello
(3): [Str1] [Str2]