web-dev-qa-db-ja.com

コンマを使用したリストの印刷C ++

私は他の言語でこれを行う方法を知っていますが、ここで使用せざるを得ないC++はそうではありません。

リストに出力する文字列のセットがあり、文字列の間にコンマが必要ですが、末尾のコンマは必要ありません。 Javaの場合、文字列ビルダーを使用し、文字列を作成した後、最後のカンマを削除するだけです。C++でそれを行うにはどうすればよいですか?

auto iter = keywords.begin();
for (iter; iter != keywords.end( ); iter++ )
{

    out << *iter << ", ";
}
out << endl;

私は最初にこのブロックを挿入しようとしました(ここにコンマ印刷を移動します)

if (iter++ != keywords.end())
    out << ", ";
iter--;

ささいなことがつまづくと嫌いになります。

編集:みんなありがとう。これが私がこのようなものをここに投稿する理由です。非常に多くの良い答えがあり、さまざまな方法で取り組みました。 Javaとアセンブリ(異なるクラス))の学期の後、4日間でC++プロジェクトを実行しなければならなかったため、ループが発生しました。答えが得られただけでなく、考える機会を得ましたこのような問題に取り組むためのさまざまな方法について。

53
quandrum

Infix_iteratorを使用します。

// infix_iterator.h 
// 
// Lifted from Jerry Coffin's 's prefix_ostream_iterator 
#if !defined(INFIX_ITERATOR_H_) 
#define  INFIX_ITERATOR_H_ 
#include <ostream> 
#include <iterator> 
template <class T, 
          class charT=char, 
          class traits=std::char_traits<charT> > 
class infix_ostream_iterator : 
    public std::iterator<std::output_iterator_tag,void,void,void,void> 
{ 
    std::basic_ostream<charT,traits> *os; 
    charT const* delimiter; 
    bool first_elem; 
public: 
    typedef charT char_type; 
    typedef traits traits_type; 
    typedef std::basic_ostream<charT,traits> ostream_type; 
    infix_ostream_iterator(ostream_type& s) 
        : os(&s),delimiter(0), first_elem(true) 
    {} 
    infix_ostream_iterator(ostream_type& s, charT const *d) 
        : os(&s),delimiter(d), first_elem(true) 
    {} 
    infix_ostream_iterator<T,charT,traits>& operator=(T const &item) 
    { 
        // Here's the only real change from ostream_iterator: 
        // Normally, the '*os << item;' would come before the 'if'. 
        if (!first_elem && delimiter != 0) 
            *os << delimiter; 
        *os << item; 
        first_elem = false; 
        return *this; 
    } 
    infix_ostream_iterator<T,charT,traits> &operator*() { 
        return *this; 
    } 
    infix_ostream_iterator<T,charT,traits> &operator++() { 
        return *this; 
    } 
    infix_ostream_iterator<T,charT,traits> &operator++(int) { 
        return *this; 
    } 
};     
#endif 

使用法は次のようになります。

#include "infix_iterator.h"

// ...
std::copy(keywords.begin(), keywords.end(), infix_iterator(out, ","));
50
Jerry Coffin

C++ 17対応の実験的コンパイラが間もなく登場するので、 std::experimental::ostream_joiner

#include <algorithm>
#include <experimental/iterator>
#include <iostream>
#include <iterator>

int main()
{
    int i[] = {1, 2, 3, 4, 5};
    std::copy(std::begin(i),
              std::end(i),
              std::experimental::make_ostream_joiner(std::cout, ", "));
}

GCC 6.0 SVNClang 3.9 SVN

24
TemplateRex

誰もがwhileループでこれを行うことにしたので、forループの例を挙げます。

for (iter = keywords.begin(); iter != keywords.end(); iter++) {
  if (iter != keywords.begin()) cout << ", ";
  cout << *iter;
}
24
Jamie Wong

一般的な方法の1つは、ループの前に最初の項目を印刷し、残りの項目のみをループして、残りの各項目の前にカンマをPRE印刷することです。

あるいは、行の現在の状態(endlの前)を維持し、適切な場所にコンマを配置する独自のストリームを作成できるはずです。

編集:T.E.Dの提案に従って、中間テスト済みのループを使用することもできます。それは次のようなものです:

if(!keywords.empty())
{
    auto iter = keywords.begin();
    while(true)
    {
        out << *iter;
        ++iter;
        if(iter == keywords.end())
        {
            break;
        }
        else
        {
            out << ", ";
        }
    }
}

「ループの前に最初の項目を印刷する」方法を最初に説明しました。これは、ループ本体を本当にシンプルに保つためですが、どの方法でもうまく機能します。

17
Mark B

漠然と通常の出力ストリームを想定しているため、空の文字列を書き込んでも実際には何も起こりません。

const char *padding = "";
for (auto iter = keywords.begin(); iter != keywords.end(); ++iter) {
    out << padding << *iter;
    padding = ", "
}
17
Steve Jessop

このようなもの?

while (iter != keywords.end())
{
 out << *iter;
 iter++;
 if (iter != keywords.end()) cout << ", ";
}
7
Klark

賢い解決策はたくさんありますが、コンパイラーにその仕事をさせずに、救いの希望を超えてコードを壊してしまうほど多くの解決策があります。

The明白な解決策は、最初の反復を特殊なケースにすることです:

bool first = true;
for (auto const& e: sequence) {
   if (first) { first = false; } else { out << ", "; }
   out << e;
}

次のような単純なパターンです。

  1. ループを壊すことはありません。各要素が反復されることは一目で明らかです。
  2. elseブロックとループ本体には任意のステートメントを含めることができるため、セパレーターを配置したり、実際にリストを印刷する以上のことができます。

これは絶対的に最も効率的なコードではないかもしれませんが、単一の十分に予測されたブランチの潜在的なパフォーマンスの損失は、巨大な巨大なstd::ostream::operator<<

6
Matthieu M.

任意の言語で)セパレータを実行するための私の典型的な方法は、中間テスト済みのループを使用することです。 C++コードは次のようになります。

for (;;) {
   std::cout << *iter;
   if (++iter == keywords.end()) break;
   std::cout << ",";
}

(注:キーワードが空の場合は、ループの前に追加のifチェックが必要です)

示されている他のソリューションのほとんどは、ループの反復ごとに追加のテスト全体を実行することになります。あなたはI/Oをしているので、それにかかる時間は大きな問題ではありませんが、それは私の感受性を害します。

5
T.E.D.

これを試して:

typedef  std::vector<std::string>   Container;
typedef Container::const_iterator   CIter;
Container   data;

// Now fill the container.


// Now print the container.
// The advantage of this technique is that ther is no extra test during the loop.
// There is only one additional test !test.empty() done at the beginning.
if (!data.empty())
{
    std::cout << data[0];
    for(CIter loop = data.begin() + 1; loop != data.end(); ++loop)
    {
        std::cout << "," << *loop;
    }
}
3
Martin York

値がstd::stringsの場合、これを range-v を使用して宣言的なスタイルでうまく記述できます。

#include <range/v3/all.hpp>
#include <vector>
#include <iostream>
#include <string>

int main()
{
    using namespace ranges;
    std::vector<std::string> const vv = { "a","b","c" };

    auto joined = vv | view::join(',');

    std::cout << to_<std::string>(joined) << std::endl;
}

文字列に変換する必要がある他のタイプの場合は、to_stringを呼び出す変換を追加するだけです。

#include <range/v3/all.hpp>
#include <vector>
#include <iostream>
#include <string>

int main()
{
    using namespace ranges;
    std::vector<int> const vv = { 1,2,3 };

    auto joined = vv | view::transform([](int x) {return std::to_string(x);})
                     | view::join(',');
    std::cout << to_<std::string>(joined) << std::endl;
}
2
Jens

使用している_++_演算子に少し問題があります。

あなたが試すことができます:

_if (++iter != keywords.end())
    out << ", ";
iter--;
_

このように、_++_は、イテレータをkeywords.end()と比較する前に評価されます。

2

ループ内にifを配置しないようにするために、これを使用します。

vector<int> keywords = {1, 2, 3, 4, 5};

if (!keywords.empty())
{
    copy(keywords.begin(), std::prev(keywords.end()), 
         std::ostream_iterator<int> (std::cout,", "));
    std::cout << keywords.back();
}

これは、ベクタータイプintによって異なりますが、ヘルパーを使用して削除できます。

2
Grim Fandango

ラムダを使用して最初の文字を切り替えるだけにすることをお勧めします。

std::function<std::string()> f = [&]() {f = [](){ return ","; }; return ""; };                  

for (auto &k : keywords)
    std::cout << f() << k;
2
lewohart

pythonに次のように書きます:

print ", ".join(keywords)

なぜそうではないのですか?

template<class S, class V>
std::string
join(const S& sep, const V& v)
{
  std::ostringstream oss;
  if (!v.empty()) {
    typename V::const_iterator it = v.begin();
    oss << *it++;
    for (typename V::const_iterator e = v.end(); it != e; ++it)
      oss << sep << *it;
  }
  return oss.str();
}

そして、ちょうどそれを次のように使用します:

cout << join(", ", keywords) << endl;

python上記の例とは異なり、" "は文字列であり、keywordsは文字列の反復可能である必要があります。このC++の例では、セパレーターとkeywordsはストリーミング可能なものであれば何でもかまいません。

cout << join('\n', keywords) << endl;
2
Darko Veberic

以下は行う必要があります:-

 const std::vector<__int64>& a_setRequestId
 std::stringstream strStream;
 std::copy(a_setRequestId.begin(), a_setRequestId.end() -1, std::ostream_iterator<__int64>(strStream, ", "));
 strStream << a_setRequestId.back();
1
leoismyname

これはうまくいくと思います

while (iter != keywords.end( ))
{

    out << *iter;
    iter++ ;
    if (iter != keywords.end( )) out << ", ";
}
1
Anycorn

そのために少しヘルパークラスを使用します。

class text_separator {
public:
    text_separator(const char* sep) : sep(sep), needsep(false) {}

    // returns an empty string the first time it is called
    // returns the provided separator string every other time
    const char* operator()() {
        if (needsep)
            return sep;
        needsep = true;
        return "";
    }

    void reset() { needsep = false; }

private:
    const char* sep;
    bool needsep;
};

それを使用するには:

text_separator sep(", ");
for (int i = 0; i < 10; ++i)
    cout << sep() << i;
1
Ferruccio

そうかもしれない.

bool bFirst = true;
for (auto curr = keywords.begin();  curr != keywords.end(); ++curr) {
   std::cout << (bFirst ? "" : ", ") << *curr;
   bFirst = false;
}
1
JohnMcG

ブーストの使用:

std::string add_str("");
const std::string sep(",");

for_each(v.begin(), v.end(), add_str += boost::lambda::ret<std::string>(boost::lambda::_1 + sep));

そして、コンマで区切られたベクトルを含む文字列を取得します。

編集:最後のカンマを削除するには、次のように発行します:

add_str = add_str.substr(0, add_str.size()-1);
1
Florin M

ファンクタを使用できます。

#include <functional>

string getSeparatedValues(function<bool()> condition, function<string()> output, string separator)
{
    string out;
    out += output();
    while (condition())
        out += separator + output();
    return out;
}

例:

if (!keywords.empty())
{
    auto iter = keywords.begin();
    cout << getSeparatedValues([&]() { return ++iter != keywords.end(); }, [&]() { return *iter; }, ", ") << endl;
}
0
Pat-Laugh

これはストリーム演算子をオーバーロードします。はい、グローバル変数は悪です。

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>

int index = 0;
template<typename T, template <typename, typename> class Cont>
std::ostream& operator<<(std::ostream& os, const Cont<T, std::allocator<T>>& vec)
{
    if (index < vec.size()) {
        if (index + 1 < vec.size())
            return os << vec[index++] << "-" << vec;
        else
            return os << vec[index++] << vec;
    } else return os;
}

int main()
{
    std::vector<int> nums(10);
    int n{0};
    std::generate(nums.begin(), nums.end(), [&]{ return n++; });
    std::cout << nums << std::endl;
}
0
user1508519

使用できる2つの方法を以下に示します。どちらも基本的に同じ考え方です。これらのメソッドには、不要な条件チェックや割り当て操作が含まれていないため、気に入っています。私は最初のものprint first methodを呼び出します。

方法1:最初の印刷方法

if (!keywords.empty()) {
    out << *(keywords.begin()); // First element.
    for (auto it = ++(keywords.begin()); it != keywords.end(); it++)
        out << ", " << *it; // Every subsequent element.
}

これは私が最初に使用した方法です。コンテナの最初の要素を単独で印刷し、その後にコンマとスペースを前に付けたすべての要素を印刷します。それはシンプルで簡潔、そしてそれがあなたがそれをするのに必要なすべてであるならば、素晴らしい働きをします。最後の要素の前に「and」を追加するなど、さらに多くのことを実行したい場合、このメソッドでは不十分です。最後の要素にあるかどうか、各ループの反復をチェックする必要があります。リストの後にピリオドや改行を追加してもそれほど悪くはありません。 forループの後に1行追加するだけで、必要なものをリストに追加できます。

2番目の方法は私がもっと好きです。最初のものと同じことを行いますが、逆の順序でprint last methodを呼び出します。

方法2:最後の印刷方法

if (!keywords.empty()) {
    auto it = keywords.begin(), last = std::prev(keywords.end());
    for (; it != last; it++) // Every preceding element.
        out << *it << ", ";
    out << "and " << *it << ".\n"; // Last element.
}

これは、最後を除くすべての要素をカンマとスペースで印刷することで機能し、オプションでその前に「and」、その後にピリオド、および/または改行文字を追加できます。ご覧のとおり、このメソッドは、ループのパフォーマンスに影響を与えたり、コードを追加したりせずに、最後の要素を処理する方法について、より多くのオプションを提供します。

Forループの最初の部分を空のままにしておくのが面倒な場合は、次のように書くことができます。

if (!keywords.empty()) {
    auto it, last;
    for (it = keywords.begin(), last = std::prev(keywords.end()); it != last; it++)
        out << *it << ", ";
    out << "and " << *it << ".\n";
}
0
apokaliptis

C++ 11ラムダとマクロの組み合わせ:

#define INFIX_PRINTER(os, sep)([&]()->decltype(os)&{static int f=1;os<<(f?(f=0,""):sep);return os;})()

使用法:

for(const auto& k: keywords)
    INFIX_PRINTER(out, ", ") << k;
0
rahnema1

私はこのような簡単な解決策を採用し、すべてのイテレータで機能するはずです。

int maxele = maxele = v.size() - 1;
for ( cur = v.begin() , i = 0; i < maxele ; ++i)
{
    std::cout << *cur++ << " , ";
}
if ( maxele >= 0 )
{
  std::cout << *cur << std::endl;
}
0
nsivakr

ifを回避する別の可能な解決策

Char comma = '[';
for (const auto& element : elements) {
    std::cout.put(comma) << element;
    comma = ',';
}
std::cout.put(']');

ループで何をしているかに依存します。

0
Matt Clarkson

doループを使用して、最初の反復のループ条件を書き換え、短絡&&演算子と有効なストリームがtrueであることを使用できます。

auto iter = keywords.begin();
if ( ! keywords.empty() ) do {
    out << * iter;
} while ( ++ iter != keywords.end() && out << ", " );
out << endl;
0
Potatoswatter