web-dev-qa-db-ja.com

foreach構文を使用して反復するときに、最後の要素にいるかどうかを確認するにはどうすればよいですか?

例えば:

for( auto &iter: item_vector ) {
     if(not_on_the_last_element) printf(", ");
}

または

for( auto &iter: skill_level_map ) {
     if(not_on_the_last_element) printf(", ");
}
19
Llamageddon

あなたは本当にできない。それは範囲の要点のようなものです。つまり、イテレータは必要ありません。しかし、コンマを印刷する方法のロジックを変更して、それが最初でなければ、印刷することができます。

bool first = true;
for (auto& elem : item_vector) {
    if (!first) printf(", ");
    // print elem
    first = false;
}

とにかくそれがループの意図であるなら。または、住所を比較することもできます。

for (auto& elem : item_vector) {
    if (&elem != &item_vector.back()) printf(", ");
    // ...
}
28
Barry

素晴らしい方法はありません。しかし、コンテナの最後の要素に簡単にアクセスできる場合は...

std::vector<int> item_vector = ...;
for (auto & elem : item_vector) {
    ...
    if (&elem != &item_vector.back())
        printf(", ");
}
7
Bill Lynch

これらのタイプのループは、「Loop and a Half」構造を使用して作成するのが最適です。

#include <iostream>
#include <vector>

int main()
{
    auto somelist = std::vector<int>{1,2,3,4,5,6,6,7,8,9,6};

    auto first = begin(somelist), last = end(somelist);
    if (first != last) {                // initial check
        while (true) {
            std::cout << *first++;     
            if (first == last) break;   // check in the middle
            std::cout << ", ";
        }
    }
}

実際の例 印刷

1、2、3、4、5、6、6、7、8、9、6

つまり、最後の要素の終わりにセパレータがありません。

真ん中のチェックは、これをdo-while(前にチェック)またはfor_each /範囲ベースの(最後にチェック)と異なるものにします。これらのループで通常のforループを強制しようとすると、余分な条件分岐または重複したプログラムロジックが導入されます。

5
TemplateRex

ユーティリティの小さなバッグのヘッダーファイルにこのコードを安全に保存します。

namespace detail {
    template<class Iter>
    struct sequence_emitter
    {
        sequence_emitter(Iter first, Iter last, std::string sep)
        : _first(std::move(first))
        , _last(std::move(last))
        , _sep(std::move(sep))
        {}

        void write(std::ostream& os) const {
            bool first_element = true;
            for (auto current = _first ; current != _last ; ++current, first_element = false)
            {
                if (!first_element)
                    os << _sep;
                os << *current;
            }
        }

    private:
        Iter _first, _last;
        std::string _sep;
    };

    template<class Iter>
    std::ostream& operator<<(std::ostream& os, const sequence_emitter<Iter>& se) {
        se.write(os);
        return os;
    }
}

template<class Iter>
detail::sequence_emitter<Iter>
emit_sequence(Iter first, Iter last, std::string separator = ", ")
{
    return detail::sequence_emitter<Iter>(std::move(first), std::move(last), std::move(separator));
}

次に、次のように末尾のセパレータなしで任意のコンテナの任意の範囲を出力できます。

vector<int> x { 0, 1, 2, 3, 4, 5 };
cout << emit_sequence(begin(x), end(x)) << endl;

set<string> s { "foo", "bar", "baz" };

cout << emit_sequence(begin(s), end(s), " comes before ") << endl;

予想される出力:

0, 1, 2, 3, 4, 5
bar comes before baz comes before foo
2
Richard Hodges

これは状態パターンに似ています。

#include <iostream>
#include <vector>
#include <functional>

int main() {
    std::vector<int> example = {1,2,3,4,5};

    typedef std::function<void(void)> Call;
    Call f = [](){};
    Call printComma = [](){ std::cout << ", "; };
    Call noPrint = [&](){ f=printComma; };
    f = noPrint;

    for(const auto& e:example){
        f();
        std::cout << e;
    }

    return 0;
}


Output:

1, 2, 3, 4, 5

fを最初に通過するときはnoPrintを指すfを指し、次にprintCommaを指すので、コンマは2番目以降の項目の前にのみ出力されます。

2
quamrana

範囲ベースのforループは、範囲全体を反復するように作成されます。それが必要ない場合は、通常のforループを実行してみませんか?

auto end = vector.end() - 1;
for (auto iter = vector.begin(); iter != end; ++iter) {
   // do your thing
   printf(", ");
}
// do your thing for the last element

「やりたいこと」を行うためにコードを2回繰り返したくない場合は、呼び出しを行うラムダを作成します。

auto end = vector.end() - 1;
// create lambda
for (auto iter = vector.begin(); iter != end; ++iter) {
   lambda(*iter);
   printf(", ");
}
lambda(vector.back());
0
peroket