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


81

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

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

还有其他人吗?


为什么称此函数为内爆?
上校恐慌

5
@ColonelPanic,类似于PHP的implode()方法,该方法连接数组元素并将其作为单个字符串输出。我不知道你为什么要问这个问题:)
ezpresso '19

Answers:


133

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

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

另请参阅此问题


54
建议包含并链接庞大的boost库以创建简单的字符串是荒谬的。
朱利安

8
@Julian大多数项目已经做到了。我同意,STL还没有包含执行此操作的方法是荒谬的。我可能也同意,这不应是最佳答案,但是显然可以找到其他答案。
谭河

我同意@Julian。Boost可能使用起来很优雅,但是就开销而言,它绝不是“最优雅的方式”。在这种情况下,这是OP算法的解决方法,而不是问题本身的解决方案。
Azeroth2b

3
大多数Boost库都是仅标头的,因此没有链接。有些甚至进入标准。
jbruni

27
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>

如果您想要一个干净的结尾(没有结尾的定界符),请看这里


9
记住,虽然,它会增加额外的分隔符(在第二个参数std::ostream_iterator在流的末尾构造函数。
迈克尔Krelin -黑客

9
“内爆”的要点是不应最后添加定界符。不幸的是,该答案最后添加了该定界符。
乔尼

20

您应该使用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)。

1
这已经书面或使用一个:stackoverflow.com/questions/3496982/...
杰里棺材

13

因为我喜欢单行代码(正如您将在最后看到的,它们对于各种奇怪的东西都非常有用),所以这是一个使用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; 
    } )

我发现此语法对流运算符很有用,在这种情况下,我不想使所有奇怪的逻辑超出流操作的范围,而只是执行简单的字符串连接。例如,考虑以下方法的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();

更新:

如@plexando在评论中所指出的,由于数组“空头”的检查遗漏了先前的空头,并且没有其他字符,因此上述代码在数组以空字符串开头时会出现错误行为。在所有运行中都检查“首次运行”是很奇怪的(即代码未优化)。

如果我们知道列表中至少包含一个元素,那么解决这两个问题就很容易。OTOH,如果我们知道该列表没有至少一个元素,那么我们可以缩短运行时间。

我认为生成的代码不太漂亮,因此我将其添加为“正确的解决方案”,但我认为上面的讨论仍然有其优点:

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的建议。我仍在尝试弄清这里发生了什么。

4
不要accumulate用于字符串。其他大多数答案都是O(n),但是accumulate是O(n ^ 2),因为它在附加每个元素之前会先对累加器进行临时复制。不,移动语义没有帮助。
Oktalist 2013年

2
@Oktalist,我不确定您为什么这么说-cplusplus.com/reference/numeric/accumulate说“复杂度在第一个和最后一个之间是线性的”。
Guss 2013年

1
假设每个单独的添加都需要固定的时间。如果T过载operator+(像string这样),或者如果您提供自己的函子,那么所有投注都将关闭。尽管我可能急于说移动语义无济于事,但在我检查过的两个实现中,它们不能解决问题。看到我对类似 问题的回答
Oktalist 2013年

1
skwllsp的评论与之无关。就像我说的,大多数其他答案(以及OP的implode示例)都在做正确的事情。即使它们不调用reserve字符串,它们也为O(n)。仅使用累加的解为O(n ^ 2)。不需要C样式的代码。
Oktalist 2013年

12
我做了一个基准测试,并且积累实际上比O(n)字符串流快。
kirbyfan64sos'3

10

简单的愚蠢解决方案呢?

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

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

6

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

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

空范围很简单:返回“”。

单元素或多元素可以通过以下方式完美处理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



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

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 "";

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

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

0

这就是我使用的方式,简单而灵活

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

-1

只需添加!字符串s =“”;

for (int i = 0; i < doc.size(); i++)   //doc is the vector
    s += doc[i];

-1

试试这个,但是使用向量而不是列表

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()+ "]";
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.