Ответ 1
Это может быть не очевидно, но, добавляя строки в implode
, вы делаете много распределений памяти и освобождений. Возможное улучшение - сделать reserve()
для s
один раз, а затем все ваши добавления.
Я ищу самый элегантный способ вставить вектор строк в строку. Ниже приведено решение, которое я использую сейчас:
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);
}
Есть ли там другие?
Это может быть не очевидно, но, добавляя строки в implode
, вы делаете много распределений памяти и освобождений. Возможное улучшение - сделать reserve()
для s
один раз, а затем все ваши добавления.
Используйте boost::algorithm::join(..)
:
#include <boost/algorithm/string/join.hpp>
...
std::string joinedString = boost::algorithm::join(elems, delim);
См. также этот вопрос.
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));
(включить <string>
, <vector>
, <sstream>
и <iterator>
)
Если вы хотите иметь чистый конец (без ограничителя), посмотрите здесь
Вы должны использовать std::ostringstream
вместо std::string
для сборки вывода (тогда вы можете вызвать его метод str()
в конце, чтобы получить строку, поэтому ваш интерфейс не должен меняться, а только временный s
)).
Оттуда вы можете перейти к использованию std::ostream_iterator
, например:
copy(elems.begin(), elems.end(), ostream_iterator<string>(s, delim));
Но это имеет две проблемы:
delim
теперь должен быть const char*
, а не один char
. Нет большой сделки.std::ostream_iterator
записывает разделитель после каждого отдельного элемента, включая последний. Таким образом, вам нужно либо удалить последний в конце, либо написать собственную версию итератора, которая не имеет этой досады. Было бы полезно сделать последнее, если у вас много кода, который нуждается в таких вещах; в противном случае можно было бы избежать всего беспорядка (т.е. использовать ostringstream
, но не ostream_iterator
).Потому что я люблю однострочники (они очень полезны для всех видов странных вещей, как вы увидите в конце), здесь решение с использованием std:: accumulate и С++ 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;
} )
Я нахожу этот синтаксис полезным для оператора потока, где я не хочу, чтобы из потока выполнялись все виды странной логики вне сферы действия, просто для простого объединения строк. Рассмотрим, например, этот оператор return из метода, который форматирует строку с использованием операторов потока (с использованием 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();
string join(const vector<string>& vec, const char* delim)
{
stringstream res;
copy(vec.begin(), vec.end(), ostream_iterator<string>(res, delim));
return res.str();
}
Версия, использующая 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";
}
Специально с большими коллекциями вы хотите избежать проверки того, хотите ли вы добавить первый элемент или нет, чтобы не было трейлинг-разделителя...
Итак, для пустого или одноэлементного списка нет итерации вообще.
Пустые диапазоны тривиальны: return "".
Одиночный элемент или многоэлемент можно отлично обрабатывать с помощью 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;
});
};
Запуск образца (требуется С++ 14): http://cpp.sh/8uspd
Вот еще один, который не добавляет разделитель после последнего элемента:
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 "";
как насчет простого глупого решения?
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;
}
Использование части этого ответа на другой вопрос дает вам объединенное, основанное на разделителе без запятой,
Использование:
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
Код:
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();
}
}
Немного длинное решение, но не использует std::ostringstream
и не требует взлома для удаления последнего разделителя.
И код:
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()));
}
просто добавьте!! Строка s = "";
for (int i = 0; i < doc.size(); i++) //doc is the vector
s += doc[i];
Вот то, что я использую, просто и гибко
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;
}
с помощью:
string a = joinList({ "a", "bbb", "c" }, "[email protected]#");
выход:
[email protected]#[email protected]#c
Во-первых, для этого требуется класс потока ostringstream
для многократного конкатенации и сохранения основной проблемы избыточного выделения памяти.
код:
string join(const vector<string>& vec, const char* delim)
{
ostringstream oss;
if(!string_vector.empty()) {
copy(string_vector.begin(),string_vector.end() - 1, ostream_iterator<string>(oss, delim.c_str()));
}
return oss.str();
}
vector<string> string_vector {"1", "2"};
string delim("->");
string joined_string = join(); // get "1->2"
Пояснение:
думая, лечите oss
здесь как std::cout
когда мы хотим написать:
std::cout << string_vector[0] << "->" << string_vector[1] << "->"
,
мы можем использовать следующие классы STL в качестве справки:
ostream_iterator
возвращает завернутый выходной поток с разделителями, автоматически добавляемыми каждый раз при использовании <<
.
например,
ostream my_cout = ostream_iterator<string>(std::cout, "->")
wraps std:cout
как my_cout
поэтому каждый раз, когда вы my_cout << "string_vector[0]"
,
это означает std::cout << "string_vector[0]" << "->"
Что касается
copy(vector.begin(), vector.end(), std::out);
это означает
std::cout << vector[0] << vector[1] (...) << vector[end]
Мне нравится использовать это однострочное накопление (без конечного разделителя):
std::accumulate(
std::next(elems.begin()),
elems.end(),
elems[0],
[](std::string a, std::string b) {
return a + delimiter + b;
}
);
попробуйте это, но используя вектор вместо списка
template <class T>
std::string listToString(std::list<T> l){
std::stringstream ss;
for(std::list<int>::iterator it = l.begin(); it!=l.end(); ++it){
ss << *it;
if(std::distance(it,l.end())>1)
ss << ", ";
}
return "[" + ss.str()+ "]";
}