pair
pair
型は2つの値の組を表す。
使い方
例としてpair
型の変数をPとする。
関数 | 機能 |
---|---|
pair<型1, 型2> P; | pair型の変数Pを宣言する |
pair<型1, 型2> P(a, b); | pair型の変数Pを宣言し、(a, b)で初期化する |
P.first | pair型の変数Pの1つ目の要素にアクセスする |
P.second | pair型の変数Pの2つ目の要素にアクセスする |
make_pair(a, b) | a,bを要素とするpair型の変数を作成する |
tie(a, b) = P | pair型の変数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, ...) = P | tuple型の変数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>;