えびちゃんの日記

えびちゃん(競プロ)の日記です。

リバースイテレータの解説するよ

std::sort(a.rbegin(), a.rend());

みたいなやつです。 便宜上 std::vector のようなものを考えますが、std::set とかについても同様です(random-access モノは除く)。

普通のイテレータ

まず普通のイテレータに触れましょう。

[0][1][2]...[n-1][-]
 ^ a.begin()      ^ a.end()

のように要素を指していると見ることもできますが、次のように境界を指しているとも見られます。

|0|1|2|...|n-1|
^ a.begin()   ^ a.end()

普通のイテレータで、* でアクセスすると、その境界の次にある要素を返します。

    v *it
...|i|...
   ^ it

++it をすると、その次の境界を指すようになります。

     v ++it
...|i|i+1|...
   ^ it

++ で進む方向は、begin 側から end 側です。

逆向きイテレータ

std::reverse_iterator というクラスがあって、普通のイテレータを基にしつつ逆向きに動かすようにしたものです。 a.rbegin() で返ってくるものは std::reverse_iterator(a.end()) です。

v a.rend()    v a.rbegin()
|0|1|2|...|n-1|
^ a.begin()   ^ a.end()

で、逆向きイテレータの進む向きは(通常の意味での)end から begin に進む向きです。 rbegin から rend に進む向きです。

逆向きイテレータ++ では、内部で持っているイテレータに対して -- をしています。

reverse_iterator& operator ++() {
  --it;
  return *this;
}

みたいな感じです。

* では、その境界の次(進む向き)にある要素を取り出します。

          v *a.rbegin()
|0|1|...|n-1|...
            ^ a.rbegin()
reference operator *() const {
  auto tmp = it;
  --tmp;
  return *tmp;
}

のような感じです。

という感じで、++* などがうまく定義されているので、逆向きイテレータを使って区間への操作ができます。

std::sort(a.rbegin(), a.rend());

とすると降順ソートされますが、やっていることに忠実に言えば、「後ろから見て昇順ソートする」です。 STL の関数に渡されてしまえば、逆向きだろうが関係ないので、うまくいってくれます。 * でアクセスするたびに -- が余分に呼ばれるので、std::greater<>() を渡す方法よりもほんの少し遅い傾向にありそうです?(あるいはアクセス順とかも関係ある?)

一方で、*a.rbegin() で最後の要素を取得できることから類推して、最後の要素を消したくて次のように書く人がいそうです。

a.erase(a.rbegin());  // ?

これは、できなくて、a.erase の引数は std::reverse_iterator ではなく a 自身のイテレータである必要があるためです。

a.erase(std::prev(a.end()));
// a.erase(a.end()-1);  // valid if random-access iterator

がんばりましょう。

a.rbegin().base() で返ってくるイテレータは所望のイテレータではないので注意です。 a.rbegin()std::reverse_iterator(a.end()) なので、a.end() が返ってきちゃいます。

おわり

イテレータ自体がそもそもわからないという人はというと...