std::sort は便利

先日の ABC128_B はいい感じに入れ子のソートをしたいという問題で、自分のコードvector, multimap, set でそれぞれ別の情報をもってがんばってしまったが、解説を読んだら pair を入れ子にすればそれを sort するだけでよしなにやってくれるということを初めて知った。

具体的には

vector<pair<pair<string, int>, int>> v;
sort(v.begin(), v.end());

このように書けば first.first, first.second, second の順に比較して昇順にしてくれる。(参考: pair で書き直したコード

これができるなら tuple でもいいじゃんと思ったらやはり通る。(参考: tuple で書き直したコード

そもそもなぜこういうことができるかというと、 std::sort() はソート対象のオブジェクトに operator< が定義されていればなんでもソートでき、 pairtupleoperator<入れ子になっていてもちゃんとソートできる頭のいい定義になっているため。(リファレンス: std::sort, std::pair の operator<, std::tuple の operator<

pairoperator< の返り値は

x.first < y.first || (!(y.first < x.first) && x.second < y.second)

なので、まず first で比較して、x.first = y.first なら second を比較した結果が返る。 first や second に pair が入れ子になって入っていても再帰的に比較されるのでちゃんとソートができるし、 first に等しい値が入っていたらその集合の中では second でソートされる。tupleoperator< も同様。

さらに、std::sort は比較条件を自分で指定できる。今回の例で言えば、たとえばレストラン番号、レストラン所在地、レストラン点数の順で tuple に格納したとしても、所在地、点数の順で比較するように指定できる。

typedef tuple<int, string, int> T;
bool comp(const T &a, const T &b){
  return get<1>(a) < get<1>(b) || !(get<1>(b) < get<1>(a)) && get<2>(b) < get<2>(a);
}

今回の問題では点数に関しては降順だったので、最後の比較だけ a, b の大小を逆にした。こうすることで、レストランの点数を負にして格納しておく必要がなくなる。(参考: 上記比較関数を std::sort に指定した場合のコード

全然関係ないが、最近 Rust をやりはじめた。このへん を読むと、どうやら Rust でも今回のような感覚は近そうに感じられる。とかくこういった言語知識は息をするように出し入れできるようになりたいもので、昔は高度な知識は language lawyer に聞けばよかったのかもしれないが、リファレンスが気軽に読めてコンパイラインタプリタが親切になった現在では個々人に相応の言語知識が求められるようになっているんじゃないかと思う。(この記事は全然高度ではないが…)