私が何かのベクトルを持っているとしましょう:
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
}
}
上記のコードでb
とe
の値を取得する方法を教えてください。
したがって、たとえば、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
これを標準ライブラリで解決するエレガントな方法はありますか?
これは基本的に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.
}
});
_
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
}
あなたは探している - 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の回答で示されるように)。
等しい値の範囲が短い場合、 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
の代わりにラムダを使用することもできます。
しかし、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
、そうではありませんか?
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
}
}