web-dev-qa-db-ja.com

標準ライブラリで同じ値を反復するにはどうすればよいですか?

私が何かのベクトルを持っているとしましょう:

std::vector<Foo> v;

このベクトルはソートされているため、等しい要素は互いに隣接しています。

(標準ライブラリを使用して)等しい要素を持つ範囲を表すすべてのイテレータペアを取得する最良の方法は何ですか?

while (v-is-not-processed) {
    iterator b = <begin-of-next-range-of-equal-elements>;
    iterator e = <end-of-next-range-of-equal-elements>;

    for (iterator i=b; i!=e; ++i) {
        // Do something with i
    }
}

上記のコードでbeの値を取得する方法を教えてください。

したがって、たとえば、vに次の数値が含まれている場合:

 index 0 1 2 3 4 5 6 7 8 9
 value 2 2 2 4 6 6 7 7 7 8

次に、bおよびeがループ内の要素を指すようにしたいと思います。

 iteration  b  e
 1st        0  3
 2nd        3  4
 3rd        4  6
 4th        6  9
 5th        9 10

これを標準ライブラリで解決するエレガントな方法はありますか?

40
geza

これは基本的にRange v3の _group_by_group_by(v, std::equal_to{})です。 C++ 17標準ライブラリには存在しませんが、独自の大まかな同等物を作成できます。

_template <typename FwdIter, typename BinaryPred, typename ForEach>
void for_each_equal_range(FwdIter first, FwdIter last, BinaryPred is_equal, ForEach f) {
    while (first != last) {
        auto next_unequal = std::find_if_not(std::next(first), last,
            [&] (auto const& element) { return is_equal(*first, element); });

        f(first, next_unequal);
        first = next_unequal;
    }
}
_

使用法:

_for_each_equal_range(v.begin(), v.end(), std::equal_to{}, [&] (auto first, auto last) {
    for (; first != last; ++first) {
        // Do something with each element.
    }
});
_
28
Justin

std::upper_bound イテレータを「次の」値に取得します。 std::upper_boundは、指定された値よりも大きい最初の要素へのイテレータを返します。現在の要素の値を指定すると、現在の値の末尾を1つ超えるイテレータが提供されます。それはあなたに次のようなループを与えるでしょう

iterator it = v.begin();
while (it != v.end()) {
    iterator b = it;
    iterator e = std::upper_bound(it, v.end(), *it);

    for (iterator i=b; i!=e; ++i) {
        // do something with i
    }
    it = e; // need this so the loop starts on the next value
}
25
NathanOliver

あなたは探している - std::equal_range

範囲内の値に等しいすべての要素を含む範囲を返します[最初、最後)

次のようなものが動作するはずです。

auto it = v.begin();
while (it != v.end())
{
    auto [b, e] = std::equal_range(it, v.end(), *it);
    for (; b != e; ++b) { /* do something in the range[b, e) */ }
    it = e;             // need for the beginning of next std::equal_range
}

備考:これは直感的なアプローチですが、std::equal_rangeは、firstおよびsecondイテレータ(つまり、bおよびe)を std::lower_bound および std::upper_bound 、これによりこのアプローチは 少し非効率的 になります。 firstイテレータはOPの場合は簡単にアクセスできるため、std::upper_bound forsecondイテレータのみneccesarry(@ NathanOliverの回答で示されるように)。

19
JeJo

等しい値の範囲が短い場合、 std::adjacent_find が適切に機能します。

for (auto it = v.begin(); it != v.end();) {
    auto next = std::adjacent_find(it, v.end(), std::not_equal_to<Foo>());
    for(; it != next; ++it) {

    }
}

必要に応じて、std::not_equal_toの代わりにラムダを使用することもできます。

9
Kyle

しかし、eを何も使用しなくても、この定式化は便利であり、間違いを犯しにくくなります。もう1つの方法(値の変化を確認する)は、退屈です(最後の範囲を特別に処理する必要があるため[...])

'最後の範囲を特別に処理する'の解釈方法によって異なります。

auto begin = v.begin();
// we might need some initialization for whatever on *begin...
for(Iterator i = begin + 1; ; ++i)
{
    if(i == v.end() || *i != *begin)
    {
        // handle range single element of range [begin, ???);
        if(i == v.end())
            break;
        begin = i;
        // re-initialize next range
    }
}

最後の範囲に対する特別な処理はありません-おそらく、初期化コードを2回必要とする可能性があります...

ネストされたループのアプローチ:

auto begin = v.begin();
for(;;)
{
    // initialize first/next range using *begin
    for(Iterator i = begin + 1; ; ++i)
    {
        if(i == v.end() || *i != *begin)
        {
            // handle range single element of range [begin, ???);
            if(i == v.end())
                goto LOOP_EXIT;
            begin = i;
            break;
        }
    }
}
LOOP_EXIT:
// go on
// if nothing left to do in function, we might prefer returning over going to...

もっとエレガント?確かに、私は疑っています...どちらのアプローチでも、同じ範囲を2回繰り返すことは避けます(最初に終了を見つけるために、次に実際の反復)。そして、独自のライブラリ関数を次のように作成するとします。

template <typename Iterator, typename RangeInitializer, typename ElementHandler>
void iterateOverEqualRanges
(
    Iterator begin, Iterator end,
    RangeInitializer ri, ElementHandler eh
)
{
    // the one of the two approaches you like better
    // or your own variation of...
}

その後、次のように使用できます。

std::vector<...> v;
iterateOverEqualRanges
(
    v.begin(), v.end(),
    [] (auto begin) { /* ... */ },
    [] (auto current) { /* ... */ }
);

最後に、eに似ています。 g。 std::for_each、そうではありませんか?

7
Aconcagua
for(auto b=v.begin(), i=b, e=v.end(); i!=e; b=i) {
    // initialise the 'Do something' code for another range
    for(; i!=e && *i==*b; ++i) {
        // Do something with i
    }
}
0
Walter