我正在寻找将字符串向量内化为字符串的最优雅的方式。下面是我现在使用的解决方案:
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);
}
还有其他人吗?
使用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
define in
std::accumulate(
std::next(elems.begin()),
elems.end(),
elems[0],
[](std::string a, std::string b) {
return a + delimiter + b;
}
);
因为我喜欢一行程序(它们对于各种奇怪的东西非常有用,正如您将在最后看到的那样),这里有一个使用std::accumulate和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;
} )
我发现这种语法对流操作符很有用,我不想让各种奇怪的逻辑超出流操作的范围,只是为了做一个简单的字符串连接。例如,考虑使用流操作符(使用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();
更新:
正如@plexando在注释中指出的那样,当数组以空字符串开始时,由于检查"第一次运行",上面的代码会出现错误行为。缺少以前的运行,没有导致额外的字符,而且-检查' '是第一次运行' '是很奇怪的;在所有运行(即代码未优化)。
如果我们知道列表至少有一个元素,那么这两个问题的解决方案都很容易。当然,如果我们知道列表不包含至少一个元素,那么我们可以进一步缩短运行时间。
我认为结果代码不那么漂亮,所以我在这里添加了正确的解决方案,但我认为上面的讨论仍然有可取之处:
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; });
指出:
- 对于支持直接访问第一个元素的容器,最好将其用于第三个参数,因此
alist[0]
用于vector。 - 根据评论和聊天中的讨论,lambda仍然会进行一些复制。这可以通过使用这个(不太漂亮的)lambda来最小化:
[](auto&& a, auto&& b) -> auto& { a += ','; a += b; return a; })
,它(在GCC 10上)将性能提高了10倍以上。感谢@Deduplicator的建议。我还在试图弄清楚这里发生了什么。
简单愚蠢的解决方案怎么样?
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;
}
使用fmt可以做到。
#include <fmt/format.h>
auto s = fmt::format("{}",fmt::join(elems,delim));
但是我不知道join是否会使它成为std::格式。
string join(const vector<string>& vec, const char* delim)
{
stringstream res;
copy(vec.begin(), vec.end(), ostream_iterator<string>(res, delim));
return res.str();
}
特别是对于较大的集合,您希望避免检查是否仍在添加第一个元素或确保没有尾随分隔符…
因此,对于空列表或单元素列表,根本不进行迭代。
空范围不重要:返回"。
单元素或多元素可以通过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;
});
};
运行示例(需要c++ 14): http://cpp.sh/8uspd
虽然我通常建议根据顶部答案使用Boost,但我认识到在一些项目中这是不需要的。
建议使用std::ostream_iterator
的STL解决方案不会像预期的那样工作-它会在末尾附加一个分隔符。
现在有一种方法可以在现代c++中做到这一点,使用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();
使用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";
}
下面是另一个不在最后一个元素后面添加分隔符的示例:
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 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"})
会给你2, 4, 5
.
另一个简单而好的解决方案是使用range v3。当前版本是c++ 14或更高版本,但也有更老的版本是c++ 11或更高版本。遗憾的是,c++ 20的range没有intersperse
函数。
这种方法的好处是:
- 优雅
- 轻松处理空字符串
- 处理列表的最后一个元素
- 效率。因为范围是惰性求值的。 小而有用的库
功能故障(参考):
-
accumulate
=与std::accumulate
类似,但参数是一个范围和初始值。第三个可选参数是操作符函数。 -
filter
=像std::filter
一样,过滤不符合谓词的元素。 -
intersperse
=键功能!在范围输入元素之间散布分隔符。
#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
}
编辑:正如@Franklin Yu建议的那样,只能使用std库中的std::ranges::views::join_with。但不幸的是,它只适用于c++23。由于我们使用的是c++23,我们也可以使用std::ranges::fold_left来代替std::accumulate
来创建一行表达式。std::ranges::fold_left
是rage v3的rages::accumulate
的std版本。
#include <iostream>
#include <string>
#include <vector>
#include <ranges>
#include <algorithm>
int main()
{
// Can be any std container
std::vector<std::string> a{ "Hello", "", "World", "is", "", "a", "program" };
std::string delimiter{", "};
std::string finalString =
std::ranges::fold_left(a | std::views::filter([](std::string s){return !s.empty();})
| std::views::join_with(delimiter)
, std::string()
, std::plus());
std::cout << finalString << std::endl; // Hello, World, is, a, program
}
我是这么用的,简单灵活
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" }, "!@#");
输出:a!@#bbb!@#c
使用这个答案的一部分来回答另一个问题,基于分隔符,不带逗号,
用法:
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();
}
}
如果您已经在使用c++基库(用于常用工具),则通常包含字符串处理特性。除了上面提到的Boost, Abseil还提供:
std::vector<std::string> names {"Linus", "Dennis", "Ken"};
std::cout << absl::StrJoin(names, ", ") << std::endl;
愚蠢提供:
std::vector<std::string> names {"Linus", "Dennis", "Ken"};
std::cout << folly::join(", ", names) << std::endl;
都给出了字符串"Linus, Dennis, Ken"
这在c++ 23中得到了一个方便的一行代码:
auto str = std::ranges::fold_left(elems | std::views::join_with(delim), std::string{}, std::plus<>{});
稍微长一点的解决方案,但不使用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()));
}
这可以使用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"
我使用下面的方法,在c++ 17中工作得很好。函数开始检查给定的向量是否为空,在这种情况下返回一个空字符串。如果不是这种情况,则从vector中获取第一个元素,然后从第二个元素开始迭代直到末尾,并在vector元素后面加上分隔符。
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;
}
使用例子:
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;
输出:(1):
(2): Hello
(3): [Str1] [Str2]
另一个std::accumulate解决方案,作为一个函数,模板化,以及错误捕获。
缺点,不适合大型字符串集(可能是O(n^2)),并且使用不当:
join<std::vector<std::string>>(myVec.begin(), myVec.end(), ' ')
在这里:
template<class T>
std::string join(char delimiter,
typename T::iterator begin,
typename T::iterator end) {
if(begin == end) {
return std::string();
}
if(std::next(begin) == end) {
return std::string(*begin);
}
return std::accumulate(std::next(begin),
end,
*begin,
[delimiter](const std::string a,
const std::string b) -> std::string
{return a + delimiter + b;});
}
的例子:
#include <iostream>
#include <numeric>
#include <string>
#include <vector>
// paste join template code here....
int main() {
std::vector<std::string> vec { "One", "Two", "Three" };
std::cout << "0: " << join<std::vector<std::string>>(' ',vec.begin()+0, vec.end());
std::cout << std::endl;
std::cout << "1: " << join<std::vector<std::string>>(' ',vec.begin()+1, vec.end());
std::cout << std::endl;
std::cout << "2: " << join<std::vector<std::string>>(' ',vec.begin()+2, vec.end());
std::cout << std::endl;
std::cout << "3: " << join<std::vector<std::string>>(' ',vec.begin()+3, vec.end());
std::cout << std::endl;
return 0;
}
结果:
0: One Two Three
1: Two Three
2: Three
3: