96

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

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);
}

还有其他人吗?

4

18 回答 18

131

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

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

另请参阅此问题

于 2011-06-13T17:52:00.933 回答
36
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>

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

于 2011-04-16T19:36:44.197 回答
26

你应该使用std::ostringstream而不是std::string构建输出(然后你可以str()在最后调用它的方法来获取一个字符串,所以你的界面不需要改变,只是临时的s)。

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

copy(elems.begin(), elems.end(), ostream_iterator<string>(s, delim)); 

但这有两个问题:

  1. delim现在需要是一个const char*,而不是一个char。没什么大不了。
  2. std::ostream_iterator在每个元素之后写入分隔符,包括最后一个。所以你要么需要在最后删除最后一个,要么编写你自己的迭代器版本,它没有这种烦恼。如果你有很多代码需要这样的东西,那么后者是值得的;否则最好避免整个混乱(即使用ostringstream但不使用ostream_iterator)。
于 2011-04-16T19:33:33.633 回答
16

因为我喜欢单线(它们对于各种奇怪的东西都非常有用,你会在最后看到),这里有一个使用 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 在评论中指出的那样,当数组以空字符串开头时,上面的代码会出现异常行为,因为“第一次运行”的检查缺少以前没有额外字符的运行,而且 -在所有运行中检查“首次运行”是很奇怪的(即代码未优化)。

如果我们知道列表至少有一个元素,那么这两个问题的解决方案就很容易了。OTOH,如果我们知道列表至少没有一个元素,那么我们可以进一步缩短运行时间。

我认为生成的代码不是那么漂亮,所以我在这里将其添加为The Correct Solution,但我认为上面的讨论仍然有优点:

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]对于向量也是如此。
  • 根据评论和聊天中的讨论,lambda 仍然会进行一些复制。这可以通过使用这个(不太漂亮的)lambda 来最小化:[](auto&& a, auto&& b) -> auto& { a += ','; a += b; return a; })它(在 GCC 10 上)将性能提高了 10 倍以上。感谢@Deduplicator 的建议。我仍在试图弄清楚这里发生了什么。
于 2012-08-28T08:38:07.853 回答
15

简单的愚蠢解决方案呢?

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;
}
于 2018-01-18T12:57:48.730 回答
15

我喜欢使用这种单行累积(没有尾随分隔符):

std::accumulate(
    std::next(elems.begin()), 
    elems.end(), 
    elems[0], 
    [](std::string a, std::string b) {
        return a + delimiter + b;
    }
);
于 2019-10-19T23:34:34.583 回答
10
string join(const vector<string>& vec, const char* delim)
{
    stringstream res;
    copy(vec.begin(), vec.end(), ostream_iterator<string>(res, delim));
    return res.str();
}
于 2013-08-25T08:52:02.297 回答
7

使用 fmt 你可以做到。

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

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

于 2021-01-05T21:43:10.950 回答
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;
         });
};

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

于 2016-09-13T10:16:48.960 回答
3

使用的版本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";
}
于 2011-04-18T19:37:08.903 回答
2

这是另一个在最后一个元素之后不添加分隔符的示例:

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 "";
于 2014-09-02T23:18:56.333 回答
2

使用这个答案的一部分来回答另一个问题会给你一个加入这个,基于没有尾随逗号的分隔符,

用法:

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();
    }
}
于 2018-06-18T16:09:31.737 回答
1

三元运算符的可能解决方案?:

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

于 2020-12-17T02:40:50.957 回答
1

虽然我通常会根据最佳答案推荐使用 Boost,但我认识到在某些项目中这是不需要的。

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

现在有一种方法可以用现代 C++ 做到这一点,但是,使用https://en.cppreference.com/w/cpp/experimental/ostream_joiner

std::ostringstream outstream;
std::copy(strings.begin(),
          strings.end(),
          std::experimental::make_ostream_joiner(outstream, delimiter.c_str()));
return outstream.str();
于 2021-12-26T18:52:24.153 回答
1

这是我用的,简单灵活

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
于 2016-03-29T09:51:29.047 回答
1

另一个简单而好的解决方案是使用范围 v3。当前版本是 C++14 或更高版本,但也有 C++11 或更高版本的旧版本。不幸的是,C++20 范围没有这个intersperse功能。

这种方法的好处是:

  • 优雅的
  • 轻松处理空字符串
  • 处理列表的最后一个元素
  • 效率。因为范围是惰性评估的。
  • 小而有用的图书馆

功能分解(参考):

  • accumulate= 类似于,std::accumulate但参数是范围和初始值。还有一个可选的第三个参数是操作符函数。
  • filter= Like 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
}
于 2021-05-08T17:33:26.737 回答
0

略长的解决方案,但不使用std::ostringstream,并且不需要破解来删除最后一个分隔符。

http://www.ideone.com/hW1M9

和代码:

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()));
}
于 2011-04-16T20:16:25.220 回答
0

这可以使用 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"
于 2021-02-15T12:37:55.653 回答