Как построить std::string из std::vector <string>?
Я хотел бы построить std::string
из std::vector<std::string>
.
Я мог бы использовать std::stringsteam
, но представьте, что существует более короткий путь:
std::string string_from_vector(const std::vector<std::string> &pieces) {
std::stringstream ss;
for(std::vector<std::string>::const_iterator itr = pieces.begin();
itr != pieces.end();
++itr) {
ss << *itr;
}
return ss.str();
}
Как еще я могу это сделать?
Ответы
Ответ 1
Вы можете использовать стандартную функцию std::accumulate()
из заголовка <numeric>
(она работает, потому что перегрузка operator +
определена для string
, который возвращает конкатенацию двух своих аргументов):
#include <vector>
#include <string>
#include <numeric>
#include <iostream>
int main()
{
std::vector<std::string> v{"Hello, ", " Cruel ", "World!"};
std::string s;
s = accumulate(begin(v), end(v), s);
std::cout << s; // Will print "Hello, Cruel World!"
}
В качестве альтернативы вы можете использовать более эффективный небольшой for
цикл:
#include <vector>
#include <string>
#include <iostream>
int main()
{
std::vector<std::string> v{"Hello, ", "Cruel ", "World!"};
std::string result;
for (auto const& s : v) { result += s; }
std::cout << result; // Will print "Hello, Cruel World!"
}
Ответ 2
С++ 03
std::string s;
for (std::vector<std::string>::const_iterator i = v.begin(); i != v.end(); ++i)
s += *i;
return s;
С++ 11 (подмножество MSVC 2010)
std::string s;
std::for_each(v.begin(), v.end(), [&](const std::string &piece){ s += piece; });
return s;
С++ 11
std::string s;
for (const auto &piece : v) s += piece;
return s;
Не используйте std::accumulate
для конкатенации строк, это классический алгоритм Шлемеля-Пейнтера, даже хуже, чем обычный пример, использующий strcat
в C. Без С++ 11 семантики перемещения, он вызывает две ненужные копии аккумулятора для каждого элемент вектора. Даже с семантикой перемещения, он все еще содержит одну ненужную копию аккумулятора для каждого элемента.
Три приведенных выше примера O (n).
std::accumulate
- O (n²) для строк.
Вы можете сделать std::accumulate
O (n) для строк, предоставив пользовательский функтор:
std::string s = std::accumulate(v.begin(), v.end(), std::string{},
[](std::string &s, const std::string &piece) -> decltype(auto) { return s += piece; });
Обратите внимание, что s
должен быть ссылкой на неконстантный, лямбда-тип возвращаемого значения должен быть ссылкой (следовательно, decltype(auto)
), и тело должно использовать +=
не +
.
С++ 20
В текущем проекте того, что, как ожидается, станет С++ 20, определение std::accumulate
было изменено для использования std::move
при добавлении к аккумулятору, поэтому начиная с С++ 20 и далее, accumulate
будет равно O ( n) для строк, и может использоваться как однострочный:
std::string s = std::accumulate(v.begin(), v.end(), std::string{});
Ответ 3
Почему бы просто не использовать оператор + для их добавления?
std::string string_from_vector(const std::vector<std::string> &pieces) {
return std::accumulate(pieces.begin(), pieces.end(), std::string(""));
}
std:: accumulate использует std:: plus под капотом по умолчанию, и добавление двух строк является конкатенацией в С++, поскольку оператор + перегружен для std::string.
Ответ 4
Моим личным выбором был бы цикл, основанный на диапазоне, как в ответ Oktalist.
Boost также предлагает хорошее решение:
#include <boost/algorithm/string/join.hpp>
#include <iostream>
#include <vector>
int main() {
std::vector<std::string> v{"first", "second"};
std::string joined = boost::algorithm::join(v, ", ");
std::cout << joined << std::endl;
}
Отпечатки:
первый, второй
В любом случае, я нахожу подход std::accumulate()
неправильным использованием этого алгоритма (независимо от сложности).
Ответ 5
Немного поздно вечеринке, но мне понравилось, что мы можем использовать списки инициализаторов:
std::string join(std::initializer_list<std::string> i)
{
std::vector<std::string> v(i);
std::string res;
for (const auto &s: v) res += s;
return res;
}
Затем вы можете просто вызвать (стиль Python):
join({"Hello", "World", "1"})
Ответ 6
Google Abseil имеет функцию absl:: StrJoin, которая делает то, что вам нужно.
Пример из файла header.
Обратите внимание, что разделитель также может быть ""
// std::vector<std::string> v = {"foo", "bar", "baz"};
// std::string s = absl::StrJoin(v, "-");
// EXPECT_EQ("foo-bar-baz", s);
Ответ 7
С С++ 11 способ stringstream не слишком страшен:
#include <vector>
#include <string>
#include <algorithm>
#include <sstream>
#include <iostream>
int main()
{
std::vector<std::string> v{"Hello, ", " Cruel ", "World!"};
std::stringstream s;
std::for_each(begin(v), end(v), [&s](const std::string &elem) { s << elem; } );
std::cout << s.str();
}
Ответ 8
Если не требуется завершающих пробелов, используйте accumulate
определенное в <numeric>
с лямбдой нестандартного соединения.
#include <iostream>
#include <numeric>
#include <vector>
using namespace std;
int main() {
vector<string> v;
string s;
v.push_back(string("fee"));
v.push_back(string("fi"));
v.push_back(string("foe"));
v.push_back(string("fum"));
s = accumulate(begin(v), end(v), string(),
[](string lhs, const string &rhs) { return lhs.empty() ? rhs : lhs + ' ' + rhs; }
);
cout << s << endl;
return 0;
}