kokatsu
kokatsu
このLTではD言語について熱く語ります
比較のために競プロでよく使われているC++とPythonと比較します
C++やPythonを批判する意図はございません
今回紹介するアルゴリズム・データ構造はけんちょんさん著の鹿本から引用しています
詳しく知りたい方はぜひお買い求め下さい
D言語は
基本文法がC/C++に似ていながら
Pythonのような書きやすさがある
二重forループでこのようなミスをしたことありませんか?
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; i++) {
//
}
}
競プロではC++でこのようなマクロが用いられます
#define rep(i, n) for (int i = 0; i < (int)(n); i++)
rep(i, N) {
rep(j, N) {
//
}
}
D言語ではforeachを使うことで同じようなことができます
foreach (i; 0 .. N) {
foreach (j; 0 .. N) {
//
}
}
foreachは配列に対しても使うことができます
int[] a = [3, 1, 4, 1, 5, 9, 2];
foreach (x; a) {
x.writeln;
// writeln(x);
}
配列の要素とともにインデックスを取得することもできます
int[] a = [3, 1, 4, 1, 5, 9, 2];
foreach (i, x; a) {
writeln(i, " ", x);
}
foreach_reverseを使うことで逆順もできます
foreach_reverse (i; 0 .. N) {
//
}
int[] a = [3, 1, 4, 1, 5, 9, 2];
foreach_reverse (i, x; a) {
writeln(i, " ", x);
}
それではアルゴリズム・データ構造にはいっていきます
"AAABBBBBAACDD"といった文字列を
(‘A’, 3), (‘B’, 5), (‘A’, 2), (‘C’, 1), (‘D’, 2)
のように圧縮すること
C++の場合(鹿本より)
// 長さ N の文字列 S が与えられたとする
for (int i = 0; i < N;) {
// 初めて S[j] != S[i] となる場所を求める
int j = i;
while (j < N && S[j] == S[i]) {
++j;
}
// 文字 S[i] が j - i 文字連続したことがわかる
cout << S[i] << j - i << endl;
// i を j のところへワープさせる
i = j;
}
Pythonの場合(鹿本より)
# 長さ N の文字列 S が与えられたとする
i = 0
while i < N:
# 初めて S[j] != S[i] となる場所を求める
j = i
while j < N and S[j] == S[i]:
j += 1
# 文字 S[i] が j - i 文字連続したことがわかる
print(S[i], j - i)
# i を j のところへワープさせる
i = j
D言語の場合は?
標準ライブラリに連長圧縮を求めるgroup関数があります
auto G = S.group.array;
foreach (g; G) {
writeln(g[0], g[1]);
}
D言語には競プロで使いそうな関数が標準ライブラリにあります
長さ $N$ の配列 $A$ に対して
$S[0] = 0$
$S[1] = S[0] + A[0]$
$S[2] = S[1] + A[1]$
…
$S[N] = S[N-1] + A[N-1]$
となる長さが $N + 1$ の配列 $S$ のこと
つまり$1 \leq i \leq N$において
$S[i] = A[0] + A[1] + … + A[i-1]$
となる
累積和を用意しておくことで
$0 \leq L \leq R \leq N - 1$を満たす$L$、$R$において
$A[L] + A[L+1] + … + A[R]$
を$S[R+1] - S[L]$を計算することで
高速で求めることができる
C++の場合(鹿本より)
vector<int> S(N+1, 0);
for (int i = 0; i < N; ++i) {
S[i+1] = S[i] + A[i];
}
Pythonの場合(鹿本より)
S = [0] * (N + 1)
for i in range(N):
S[i+1] = S[i] + A[i]
C++やPythonには
標準ライブラリに累積和を求める関数がある
C++の場合:exclusive_scan
A.emplace_back(0);
vector<int> S(N+1, 0);
exclusive_scan(A.begin(), A.end(), S.begin(), 0);
Pythonの場合:accumulate
S = [0] + list(itertools.accumulate(A))
D言語にも累積和を求めることができる関数がある cumulativeFold
int[] S = [0] ~ A.cumulativeFold!"a + b".array;
これらの関数を用いることで
他の累積〇〇を求めることができる
$S[0] = A[0]$
$S[1] = max(S[0], A[1])$
…
$S[N-1] = max(S[N-2], A[N-1])$
つまり$0 \leq i < N$において
$S[i] = max(A[0], A[1], …, A[i])$
となる
C++の場合
vector<int> S(N);
exclusive_scan(A.begin(), A.end(), S.begin(), A[0],
[](int a, int b) { return max(a, b); });
Pythonの場合
S = list(itertools.accumulate(A, max))
D言語の場合
int[] S = A.cumulativeFold!max.array;
cumulativeFold関数
int[] S = [0] ~ A.cumulativeFold!"a + b".array;
int[] S = A.cumulativeFold!max.array;
!の後の"a + b"やmaxはテンプレートパラメータ
配列をソートするとき
C++の場合
vector<int> A = {3, 1, 4, 1, 5, 9, 2};
sort(A.begin(), A.end()); // 昇順 [1, 1, 2, 3, 4, 5, 9]
sort(A.begin(), A.end(), greater<>()); // 降順 [9, 5, 4, 3, 2, 1, 1]
Pythonの場合
A = [3, 1, 4, 1, 5, 9, 2]
A.sort() # 昇順 [1, 1, 2, 3, 4, 5, 9]
A.sort(reverse=True) # 降順 [9, 5, 4, 3, 2, 1, 1]
D言語の場合
int[] A = [3, 1, 4, 1, 5, 9, 2];
A.sort; // 昇順 [1, 1, 2, 3, 4, 5, 9]
A.sort!"a > b"; // 降順 [9, 5, 4, 3, 2, 1, 1]
グラフの最短距離を求めるダイクストラ法を
優先度付きキューBinaryHeapを使って実装する場合
struct Edge {
int to; // 隣接頂点番号
long weight; // 重み
}
// 注意!! sortとは比較方向が異なる
// これはEdgeのweightが小さい順に慣れべている
auto heap = new BinaryHeap!(Array!Edge, "a.weight > b.weight")();
構造体の要素を用いて優先度付けすることができる
D言語の良さをわかっていただけましたでしょうか?
ご清聴ありがとうございました