文字列のベクトルを文字列に内破する方法(エレガントな方法)


83

文字列のベクトルを文字列に内破する最もエレガントな方法を探しています。以下は、私が現在使用しているソリューションです。

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()メソッドと同様に、配列要素を結合して単一の文字列として出力します。なぜあなたはこの質問をしているのだろうか:)
ezpresso19年

回答:


133

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

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

この質問も参照してください。


57
単純な文字列を作成するために大規模なブーストライブラリを含めてリンクすることを提案するのはばかげています。
ジュリアン

8
@Julianほとんどのプロジェクトはすでにこれを行っています。ただし、STLにこれを行う方法がまだ含まれていないのはばかげていることに同意します。これが一番の答えではないことにも同意するかもしれませんが、他の答えは明らかに利用可能です。
川タム

@Julianに同意します。Boostは使用中はエレガントかもしれませんが、オーバーヘッドの点で「最もエレガントな方法」ではありません。この場合、これはOPのアルゴリズムの回避策であり、質問自体の解決策ではありません。
azeroth2b 2018年

3
ほとんどのBoostライブラリはヘッダーのみであるため、リンクするものはありません。標準に入る人もいます。
jbruni

28
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
それが(第2パラメータを、余分な区切り文字を追加すること、けれども、心に留めておくstd::ostream_iteratorストリームの終わりにコンストラクタ。
マイケルKrelin -ハッカー

9
「内破」のポイントは、区切り文字を最後に追加してはならないということです。この回答は、残念ながらその区切り文字を最後に追加します。
ジョニー2015年

そして幸いなことに、私も最後にトークンを追加する必要があります!解決してくれてありがとう。
КонстантинВан

20

出力を作成するのstd::ostringstreamではなく、std::stringを使用する必要があります(その後str()、最後にそのメソッドを呼び出して文字列を取得できるため、インターフェイスを変更する必要はなく、一時的なものだけを変更する必要がありますs)。

そこから、次のstd::ostream_iteratorようにを使用するように変更できます。

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

しかし、これには2つの問題があります。

  1. delim現在はconst char*、単一ではなく、である必要がありますchar。大きな問題ではない。
  2. std::ostream_iterator最後を含むすべての要素の後に区切り文字を書き込みます。したがって、最後に最後のものを消去するか、この煩わしさのない独自のバージョンのイテレータを作成する必要があります。このようなものを必要とするコードがたくさんある場合は、後者を実行する価値があります。そうしないと、混乱全体を回避するのが最善の場合ostringstreamがあります(つまり、使用するが使用しないostream_iterator)。

1
または、すでに書かれているものを使用してください:stackoverflow.com/questions/3496982/…–
Jerry Coffin

13

私はワンライナーが大好きなので(最後に表示されるように、あらゆる種類の奇妙なものに非常に役立ちます)、std :: accumulateとC ++ 11ラムダを使用したソリューションを次に示します。

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ステートメントについて考えてみます。

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が指摘しているように、上記のコードは、配列が空の文字列で始まる場合、「最初の実行」のチェックで以前の実行が欠落しているために追加の文字がないため、誤動作が発生します。すべての実行で「最初に実行された」チェックを実行するのは奇妙です(つまり、コードが最適化されていません)。

リストに少なくとも1つの要素があることがわかっていれば、これらの問題の両方の解決策は簡単です。OTOH、リストに少なくとも1つの要素がないことがわかっている場合は、実行をさらに短縮できます。

結果のコードはそれほどきれいではないと思うので、ここに正しい解決策として追加しますが、上記の説明にはまだメリットがあると思います。

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

ノート:

  • 最初の要素への直接アクセスをサポートするコンテナーの場合、代わりに3番目の引数にそれを使用する方がおそらく良いでしょうalist[0]。ベクトルの場合も同様です。
  • コメントとチャットでの議論によると、ラムダはまだいくつかのコピーを行います。これは、代わりにこの(あまりきれいではない)ラムダを使用することで最小限に抑えることができます。[](auto&& a, auto&& b) -> auto& { a += ','; a += b; return a; })これは(GCC 10では)パフォーマンスをx10以上向上させます。提案してくれた@Deduplicatorに感謝します。私はまだここで何が起こっているのか理解しようとしています。

4
accumulate文字列には使用しないでください。他のほとんどの答えはO(n)ですが、accumulate各要素を追加する前にアキュムレータの一時的なコピーを作成するため、O(n ^ 2)です。いいえ、移動セマンティクスは役に立ちません。
オクタリスト2013

2
@Oktalist、なぜそう言うのかわかりません--cplusplus.com/reference/numeric/accumulateは「複雑さは最初と最後の間の距離で線形です」と言っています。
ガス2013

1
これは、個々の追加に一定の時間がかかることを前提としています。Tオーバーロードしている場合operator+(のようにstring)、または独自のファンクターを提供している場合は、すべての賭けが無効になります。移動セマンティクスは役に立たないと急いで言ったかもしれませんが、私がチェックした2つの実装の問題は解決されません。同様の 質問に対する私の回答を参照してください。
オクタリスト2013

1
skwllspのコメントはそれとは何の関係もありません。私が言ったように、他のほとんどの答え(そしてOPのimplode例)は正しいことをしています。reserve文字列を呼び出さなくても、O(n)です。蓄積を使用するソリューションのみがO(n ^ 2)です。Cスタイルのコードは必要ありません。
オクタリスト2013

12
ベンチマークを実行しましたが、実際には、累積はO(n)文字列ストリームよりも高速でした。
kirbyfan64sos 2015年

11

単純な愚かな解決策はどうですか?

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

7

私はこのワンライナーアキュムレートを使用するのが好きです(末尾の区切り文字なし):

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

4
空のときは注意してください。
カルロスPinzón

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

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

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

三項演算子を使用した可能な解決策?:

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


0

fmtを使用すると実行できます。

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

しかし、joinがstd :: formatに到達するかどうかはわかりません。


-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()+ "]";
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.