如何将字符串向量内嵌到字符串中(优雅的方式)

ezp*_*sso 69 c++ string stl stdstring implode

我正在寻找最优雅的方法将字符串向量内部转换为字符串.以下是我现在使用的解决方案:

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);
}
Run Code Online (Sandbox Code Playgroud)

那里还有其他人吗?

And*_*ner 120

用途boost::algorithm::join(..):

#include <boost/algorithm/string/join.hpp>
...
std::string joinedString = boost::algorithm::join(elems, delim);
Run Code Online (Sandbox Code Playgroud)

另见这个问题.

  • 建议包含和链接大量的boost库来创建一个简单的字符串是荒谬的. (28认同)
  • @Julian大多数项目已经这样做了.不过,我同意STL不包括这样做的方法是荒谬的.我可能也同意这不应该是*顶部*答案,但其他答案显而易见. (7认同)
  • 大多数 Boost 库都是头文件,所以没有什么可以链接的。有些甚至进入了标准。 (3认同)
  • stdlib 中没有这个基本功能是荒谬的。 (3认同)

seh*_*ehe 24

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));
Run Code Online (Sandbox Code Playgroud)

(包括<string>,<vector>,<sstream><iterator>)

如果你想要一个干净的结尾(没有尾随分隔符)看看这里

  • 但请记住,它会添加额外的分隔符(第二个参数到流末尾的`std :: ostream_iterator`构造函数. (5认同)
  • "内爆"的意思是最后不应添加分隔符.不幸的是,这个答案最后添加了分隔符. (5认同)

Joh*_*nck 21

您应该使用std::ostringstream而不是std::string构建输出(然后您可以str()在最后调用其方法来获取字符串,因此您的接口不需要更改,只需要临时s).

从那里,您可以改为使用std::ostream_iterator,如下所示:

copy(elems.begin(), elems.end(), ostream_iterator<string>(s, delim)); 
Run Code Online (Sandbox Code Playgroud)

但这有两个问题:

  1. delim现在需要是一个const char*,而不是一个char.没什么大不了.
  2. std::ostream_iterator在每个元素之后写入分隔符,包括最后一个元素.因此,你需要在最后删除最后一个,或者编写你自己的迭代器版本,这个版本没有这个烦恼.如果你有很多需要这样的东西的代码,那么值得做后者; 否则可能最好避免整个混乱(即使用ostringstream但不是ostream_iterator).


小智 13

我喜欢使用这种单行累加(无尾随分隔符):

std::accumulate(
    std::next(elems.begin()), 
    elems.end(), 
    elems[0], 
    [](std::string a, std::string b) {
        return a + delimiter + b;
    }
);
Run Code Online (Sandbox Code Playgroud)

  • 空的时候要小心。 (4认同)

小智 12

简单的愚蠢解决方案怎么样?

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;
}
Run Code Online (Sandbox Code Playgroud)

  • @xtofl你高估了那张支票的成本。 (2认同)
  • 除了“ret.empty()”是一个简单的检查这一事实之外,这是一个非常适合分支预测器的用例,因为在第一次测试之后它总是评估为 false。 (2认同)

Gus*_*uss 11

因为我喜欢单行(它们对于各种奇怪的东西非常有用,正如你最后会看到的),这里有一个使用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; 
    } )
Run Code Online (Sandbox Code Playgroud)

我觉得这个语法对于stream运算符很有用,我不希望在流操作的范围之外有各种奇怪的逻辑,只是做一个简单的字符串连接.例如,考虑使用流操作符格式化字符串的方法的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();
Run Code Online (Sandbox Code Playgroud)

  • 我做了[基准](https://gist.github.com/kirbyfan64/e69b605a287cedcd57f5),累积实际上比O(n)字符串流快. (10认同)
  • 不要对字符串使用 `accumulate`。大多数其他答案都是 O(n),但 `accumulate` 是 O(n^2),因为它在附加每个元素之前制作了累加器的临时副本。不,移动语义无济于事。 (4认同)
  • @Oktalist,我不知道你为什么这么说 - http://www.cplusplus.com/reference/numeric/accumulate/ 说“复杂性在第一个和最后一个之间的距离上是线性的”。 (2认同)

Bla*_*mba 9

string join(const vector<string>& vec, const char* delim)
{
    stringstream res;
    copy(vec.begin(), vec.end(), ostream_iterator<string>(res, delim));
    return res.str();
}
Run Code Online (Sandbox Code Playgroud)


xto*_*ofl 7

特别是对于较大的集合,您希望避免检查您是否仍在添加第一个元素以确保没有尾随分隔符...

所以对于空或单元素列表,根本没有迭代。

空范围是微不足道的:返回“”。

可以通过以下方式完美处理单个元素或多元素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;
         });
};
Run Code Online (Sandbox Code Playgroud)

运行示例(需要 C++14):http : //cpp.sh/8uspd


Rio*_*iot 7

虽然我通常会建议按照最佳答案使用 Boost,但我认识到在某些项目中这是不需要的。

建议使用的 STL 解决方案std::ostream_iterator不会按预期工作 - 它会在末尾附加一个分隔符。

现在有一种方法可以使用std::experimental::ostream_joiner使用现代 C++ 来做到这一点:

std::ostringstream outstream;
std::copy(strings.begin(),
          strings.end(),
          std::experimental::make_ostream_joiner(outstream, delimiter.c_str()));
return outstream.str();
Run Code Online (Sandbox Code Playgroud)


小智 6

使用 fmt 你可以做到。

#include <fmt/format.h>
auto s = fmt::format("{}",fmt::join(elems,delim)); 
Run Code Online (Sandbox Code Playgroud)

但我不知道 join 是否会使其成为 std::format。


Car*_*ten 6

这在 C++23 中得到了方便的一句:

auto str = std::ranges::fold_left(elems | std::views::join_with(delim), std::string{}, std::plus<>{});
Run Code Online (Sandbox Code Playgroud)


Kik*_*uto 5

另一个简单而好的解决方案是使用range v3。当前版本是 C++14 或更高版本,但也有旧版本是 C++11 或更高版本。不幸的是,C++20 范围没有该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
}
Run Code Online (Sandbox Code Playgroud)

编辑:正如@Franklin Yu建议的那样,只能将 std 库与std::ranges::views::join_with一起使用。但不幸的是仅适用于 c++23。由于我们使用的是 c++23,我们还可以使用std::ranges::fold_leftstd::accumulate创建单行表达式。std::ranges::fold_left是 rages v3 的标准版本rages::accumulate

#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
}
Run Code Online (Sandbox Code Playgroud)

  • C++20 确实有 [`std::ranges::views::join_with`](https://en.cppreference.com/w/cpp/ranges/join_with_view),这可能有帮助吗? (2认同)