【第4回】はじめての競技プログラミング

pair,tuple,型エイリアスなどを紹介します

Homeブログ一覧【第4回】はじめての競技プログラミング

pair

  • pair型は2つの値の組を表す。

使い方

例としてpair型の変数をPとする。

関数機能
pair<型1, 型2> P;pair型の変数Pを宣言する
pair<型1, 型2> P(a, b);pair型の変数Pを宣言し、(a, b)で初期化する
P.firstpair型の変数Pの1つ目の要素にアクセスする
P.secondpair型の変数Pの2つ目の要素にアクセスする
make_pair(a, b)a,bを要素とするpair型の変数を作成する
tie(a, b) = Ppair型の変数Pの要素をa,bに代入する

つまり、上の例で考えると、 P.first == a P.second == bということになる。

  • 型1、型2に入る型はint型やlong long型、string型,vectorなどなんでも良い。
  • また、型1と型2は同じ型でなくても良い。
  • make_pair(a, b){a, b}と書いてよい場合がある。
  • tieをする前に変数は宣言されている必要がある。
pair<T, U> P;
tie(a, b) = P;  // これはa,bが宣言されていないためエラー

使い方の例

  • 人の苗字と名前
pair<string, string> name;  // pair型の変数nameを宣言
name.first = "Yamada";  // nameの1つ目の要素にYamadaを代入
name.second = "Taro";  // nameの2つ目の要素にTaroを代入
cout << name.first << " " << name.second << endl; // Yamada Taro
  • 座標
pair<int, int> P1(10, 0), P2(0, -2);
cout << P1.first << " " << P1.second << endl; // 10 0
cout << P2.first << " " << P2.second << endl; // 0 -2
  • 人の名前と身長
pair<string, double> person;
person = make_pair("Yamada", 175.5);
cout << person.first << " " << person.second << endl;  // Yamada 175.5

問題

ヒント(pairを配列で持つ方法、pair型の配列を並べ替える方法)
  • pairを配列で持つには、vector<pair<型1, 型2>> P;とする。
  • 例えば、vector<pair<int, int>> P;とすると、P.at(i).firstでi番目の要素の1つ目の要素にアクセスできる。
  • pair型のvectorをソートするには、sort(P.begin(), P.end());とする。
解答例その1
#include <bits/stdc++.h>
using namespace std;

int main() {
  int N;
  cin >> N;
  vector<pair<int, int>> p(N);
  for (int i = 0; i < N; i++) {
    int a, b;
    cin >> a >> b;
    p.at(i) = make_pair(b, a);  // b, a の順でペアにする
  }

  sort(p.begin(), p.end());

  for (int i = 0; i < N; i++) {
    int b, a;
    tie(b, a) = p.at(i);  // b, a の順であることに注意
    cout << a << " " << b << endl;
  }
}
  • sort関数でpair型のvectorをソートするときは、デフォルトではfirstの値が小さい順にソートされ、firstの値が同じ場合はsecondの値が小さい順にソートされる。
  • 今回はbを基準にソートしたいので、p.at(i) = make_pair(b, a);としてb,aの順でペアにしている。
解答例その2(ラムダ式ソートを使う場合)
#include <bits/stdc++.h>
using namespace std;
#define rep(i, n) for (int i = 0; i < (n); ++i)
int main() {
    int n;
    cin >> n;
    vector<pair<int, int>> ab(n);
    rep(i, n) {
        int a, b;
        cin >> a >> b;
        ab[i] = { a, b };
    }
    sort(ab.begin(), ab.end(), [](pair<int, int> a, pair<int, int> b) {
        return a.second < b.second;  //  ラムダ式ソートでab.secondが小さい順にソート
    });
    rep(i, n) {
        cout << ab[i].first << " " << ab[i].second << endl; //  abを一行ずつ出力
    }
}
  • 前回習ったラムダ式ソートを使うと、firstの値をa、secondの値をbとしてソートすることができる。(この解法の方が自由度が高いため、慣れてきたらこちらを推奨します。)

tuple

  • tuple型とはpairを一般化したもの。
  • pair型では2つの要素しか持てないが、tuple型では任意の数の要素を持つことができる。

使い方

例としてtuple型の変数をPとする。

関数機能
tuple<型1, 型2, ...> P;tuple型の変数Pを宣言する
tuple<型1, 型2, ...> P(a, b, ...);tuple型の変数Pを宣言し、(a, b, ...)で初期化する
get<i>(P)tuple型の変数Pのi番目の要素にアクセスする
make_tuple(a, b, ...)a, b, ...を要素とするtuple型の変数を作成する
tie(a, b, ...) = Ptuple型の変数Pの要素をa, b, ...に代入する
  • ただし、for文を使ってtuple型の変数の要素にアクセスすることはできない。
  • iは定数である必要があり、実装時は0, 1, 2, ...などの値で書く必要がある。
int i = 3;
get<i>(P); // これはエラー
get<3>(P); // これはOK

使い方の例

  • 人の苗字、名前、年齢
tuple<string, string, int> person;
person = make_tuple("Yamada", "Taro", 20);
cout << get<0>(person) << " " << get<1>(person) << " " << get<2>(person) << endl;  // Yamada Taro 20
  • tupleを"普通の"型・pair型の代わりに使うこともできる
tuple<int> a(0);
tuple<int, int> b(1, 2);
cout << get<0>(a) << endl; // 0
cout << get<0>(b) << " " << get<1>(b) << endl;  // 1 2

問題

解答例
#include <bits/stdc++.h>
using namespace std;
#define rep(i, n) for (int i = 0; i < (n); ++i)
int main() {
    int n;
    cin >> n;
    vector<tuple<string, int, int>> abc(n);
    rep(i, n) {
        string a;
        int b;
        cin >> a >> b;
        abc[i] = { a, b, i+1 };
    }
    // 1番目の値でソート、同じなら2番目の値が大きい順でソート
    sort(abc.begin(), abc.end(), [](tuple<string, int, int> a, tuple<string, int, int> b) {
        if (get<0>(a) == get<0>(b)) {
            return get<1>(a) > get<1>(b);
        }
        else {
            return get<0>(a) < get<0>(b);
        }
    });
    // 3番目の値を出力
    rep(i, n) {
        cout << get<2>(abc[i]) << endl;
    }
}

pair/tupleを使う意味

vectorだとa[0]とかで書けて楽なのに何故pair/tupleを使うのか?

1. 要素数が変わらないから

  • vectorだとpush_back()などで要素数を増やすことができる。
  • しかし、要素数を変更するとfor文が使いにくくなる。
  • 格納データが数個程度であれば、pair/tupleを使った方が楽。

2. 複数の型を1つの変数に格納できるから

  • vectorは同じ型のデータしか格納できない。
  • それに対してpair/tupleは異なる型のデータを1つの変数に格納できる。

pair/tupleの分解

  • いちいちx = a.first, y = a.second, z = get<0>(b)みたいに書くことは面倒。

  • 構造化束縛を使うと楽に書くことができる。

  • pairの構造化束縛

pair<string, double> a("fooo", 123.4);

auto [x, y] = a;
// これは以下の文と同じ
string x = a.first;
double y = a.second;
  • tupleの構造化束縛
tuple<string, double, int> a("fooo", 123.4, 42);

auto [x, y, z] = a;
// これは以下の文と同じ
string x = get<0>(a);
double y = get<1>(a);
int z = get<2>(a);

auto型

構造化束縛でauto[x, y] = aと書いた、autoとは?

  • コンパイラが自動で型を推論してくれるので、型を省略できる場合に使える。
  • 逆に、推論できないような場所では使えない。
  • 可読性が下がってしまう可能性も…
string concat(string a, string b) {
    return a + b;
}

int main(){
  string a = "Hello";
  string b = "World";
  auto ab = concat(a, b);
  cout << ab << endl;

  vector<int> c = {1, 2, 3};
  auto d = c;

  pair<int, string> e(10, "foo");
  auto [x, y] = e;
}

  • abの型は? concat関数はstring型を返す→abはstring型!
  • dの型は? dにcを代入する処理→cと同じvector<int>型!
  • x, yの型は? pair<int, string>の1つ目がxに、2つ目がyに代入される→xはint型、yはstring型!

型エイリアス

型エイリアスとは?

  • tuple<int, int, int>や、場合によってはtuple<vector<vector<vector<…>>>>などのコードを書くことがあるが、いちいち書くのは大変。
  • 一方で、auto型を使えない/使いたくない場合もある。
  • そこで、型エイリアスを使うと、型に別名をつけることができる。

使い方

  • using <別名> = <元の型>;using namespace std; の後に書く。
  • いくつ別名を定義してもいいし、同じ型に対していくつ別名を付けても良い。
  • ただし、既に定義されている型を別の型のエイリアスとして用いることはできない。
#include <bits/stdc++.h>
using namespace std;

//いくつ書いてもOK
using ll = long long;
using lll = long long;
using ull = unsigned long long;

int main() {
  ll x = 1'000'000'000'000'000'000;
  //これは以下と同じ
  long long x = 1'000'000'000'000'000'000;
  //同じ long long 型に対して別名をつけているので、これも同じになる
  lll x = 1'000'000'000'000'000'000;
}
  • 例えば、vector<vector<vector<vector<int>>>> 型をint4Dという名前で使いたい場合、以下のように書く。
using int4D = vector<vector<vector<vector<int>>>>;
  • よく使うもの一覧(スニペットに登録しておくと便利!)
using ll = long long;
using P = pair<int, int>;
using T = tuple<int, int, int>;

練習問題