如何将一个字符串向量优雅地合并成一个字符串

127

我正在寻找将字符串向量拼接成一个字符串的最优雅方法。下面是我现在使用的解决方案:

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

还有其他人吗?


2
为什么你把这个函数叫做implode? - Colonel Panic
12
按照PHP中implode()方法的类比,它会将数组元素连接起来并将它们作为单个字符串输出。我想知道你为什么要问这个问题 :) - ezpresso
9
在Python中:'delim.join(elems)'。抱歉,我忍不住了。C++仍然没有附带电池。 :-) 这个问题在2021年已经有10年历史了,但没有一个可行且优雅的答案(尾部分隔符、过高的运行时间、比朴素实现更多的#include行……)。 - Johannes Overmann
23个回答

5
另一个简单而好的解决方案是使用ranges v3。当前版本是C++14或更高版本,但也有旧版本是C++11或更高版本。不幸的是,C++20的ranges没有intersperse函数。
这种方法的好处包括:
- 优雅 - 轻松处理空字符串 - 处理列表的最后一个元素 - 高效。因为ranges是惰性评估的。 - 小巧实用的库
函数分解(Reference):
- 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是rages 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
}

2
C++20确实具有std::ranges::views::join_with,这可能会有所帮助。 - Franklin Yu
@FranklinYu 是的,std::ranges::views::join_with是相应的函数,但仅在C++23中可用。 - Augusto Icaro

5
这在C++23中可以得到一个方便的一行代码:
auto str = std::ranges::fold_left(elems | std::views::join_with(delim), std::string{}, std::plus<>{});

fold_left的必要性并不明显,你可以解释一下为什么它在这里。 - undefined
1
fold_left是C++23引入的accumulate的广义范围版本。C++20引入了范围,但错过了一些关键部分,比如一些算法实现。C++23通过fold表达式添加了其中一些。范围库的设计是惰性的,fold_left正在评估由join_with创建的视图。否则,您只会得到一个视图,而不是一个字符串。如果您想了解更多关于C++23的fold_*新增内容的信息,我推荐阅读Sy Brand的这篇优秀文章 - undefined

4
使用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";
}

3

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

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

3
一种可能的解决方案是使用三元运算符?:
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


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

2

如果您已经使用了C++基础库(用于常用工具),则通常包含字符串处理功能。除了上面提到的Boost之外,Abseil提供

std::vector<std::string> names {"Linus", "Dennis", "Ken"};
std::cout << absl::StrJoin(names, ", ") << std::endl;

Folly提供了以下功能::

std::vector<std::string> names {"Linus", "Dennis", "Ken"};
std::cout << folly::join(", ", names) << std::endl;

两者都会输出字符串"Linus, Dennis, Ken"


2
这是我使用的东西,简单灵活。
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

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"

0
我正在使用以下方法,在C++17中运行良好。该函数开始检查给定的向量是否为空,如果是,则返回空字符串。如果不是这种情况,则从向量中获取第一个元素,然后从第二个元素迭代到末尾,并附加分隔符,后跟向量元素。
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]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接