D言語で九九表を表示
Twitterで、九九表を表示するプログラム(Java)を見かけたのでD言語でもやってみた
0. まずは文字列をベタ書き
D
void multiplicationTable0()
{
import std.stdio;
"* | 1 2 3 4 5 6 7 8 9
-----------------------------
1 | 1 2 3 4 5 6 7 8 9
2 | 2 4 6 8 10 12 14 16 18
3 | 3 6 9 12 15 18 21 24 27
4 | 4 8 12 16 20 24 28 32 36
5 | 5 10 15 20 25 30 35 40 45
6 | 6 12 18 24 30 36 42 48 54
7 | 7 14 21 28 35 42 49 56 63
8 | 8 16 24 32 40 48 56 64 72
9 | 9 18 27 36 45 54 63 72 81"
.writeln;
}
void multiplicationTable0()
{
import std.stdio;
"* | 1 2 3 4 5 6 7 8 9
-----------------------------
1 | 1 2 3 4 5 6 7 8 9
2 | 2 4 6 8 10 12 14 16 18
3 | 3 6 9 12 15 18 21 24 27
4 | 4 8 12 16 20 24 28 32 36
5 | 5 10 15 20 25 30 35 40 45
6 | 6 12 18 24 30 36 42 48 54
7 | 7 14 21 28 35 42 49 56 63
8 | 8 16 24 32 40 48 56 64 72
9 | 9 18 27 36 45 54 63 72 81"
.writeln;
}
最速のコード
改行がそのまま表示されるのが何気に便利
学校の課題をこれで提出したら(たぶん)やり直し
1. foreachで繰り返す
D
void multiplicationTable1()
{
import std.stdio;
"* |%2s".writef(1);
foreach (num; 2 .. 10)
" %2s".writef(num);
"\n-----------------------------".writeln;
foreach (col; 1 .. 10) {
"%s |%2s".writef(col, col);
foreach (row; 2 .. 10)
" %2s".writef(col * row);
writeln;
}
}
void multiplicationTable1()
{
import std.stdio;
"* |%2s".writef(1);
foreach (num; 2 .. 10)
" %2s".writef(num);
"\n-----------------------------".writeln;
foreach (col; 1 .. 10) {
"%s |%2s".writef(col, col);
foreach (row; 2 .. 10)
" %2s".writef(col * row);
writeln;
}
}
素直に繰り返しの部分をforeach
にしたバージョン
writef
(C言語のprintf
相当)の書式指定子は楽ちん
処理の流れをながめていると、大まかに3つの処理がある
(行見出しの表示、区切り線の表示、九九本体の表示)
ので、なんとなく分けて書いてみる
ロジックをわかりやすく書き直したい
上のforeach
版でもわかりやすいが、順番に並んでいる要素の最初や最後だけ
特別扱いをしてあげないといけない部分で、もやっとする
例えば行見出しの部分で、1の手前には空白が1文字、2~9の手前には空白が2文字
空いているのが気になってしまう
要は順番に並んでいる要素の、間にだけ空白を入れたいだけなのだ
2. 標準ライブラリを使用する
こういう一般的な悩み(?)は、標準ライブラリで何とかなる場合が多い
std.string
のjoin
がそのままの機能なので、
次のように書き換えてみた
void multiplicationTable2()
{
import std.stdio;
import std.string : format, join;
import std.range : iota, repeat, tee, No;
import std.algorithm : map, each;
(
"* |" ~ // * |
iota(1, 10) // 1 2 3 4 5 6 7 8 9
.map!(num => num.format!"%2s")
.join(" ")
).writeln;
"-".repeat(29).join.writeln; // -----------------------------
iota(1, 10) // # |## ## ## ## ## ## ## ## ##
.tee!(col => "%s |".writef(col), No.pipeOnPop)
.each!(col => iota(1, 10)
.map!(row => (col * row).format!"%2s")
.join(" ")
.writeln);
}
ついでにforeach
をiota
+map
/each
、tee
で置き換え
意味もなくrepeat
なども使用している
3. 標準出力への書き込み回数を減らす
ここまできたらwrite
を一回だけにしたい
1つの大きな文字列を作ってもいいが、ごちゃごちゃしそうなので
まずは、これまでの3つのまとまりをそれぞれ要素とする配列を作成して
最後にすべての要素を改行でjoin
する方向でやってみよう
(ついでにマジックナンバーを少し整理)
void multiplicationTable3()
{
import std.stdio;
import std.conv : to;
import std.string : format, join;
import std.range : iota, repeat;
import std.algorithm : map;
enum E = 9;
enum S = E * 3 + 1 + 1;
[
"* |" ~ // * |
iota(1, E + 1) // 1 2 3 4 5 6 7 8 9
.map!(num => num.format!"%2s")
.join(" "),
"-".repeat(S).join, // -----------------------------
iota(1, E + 1) // # |## ## ## ## ## ## ## ## ##
.map!(col => col.to!string ~ " |" ~
iota(1, E + 1)
.map!(row => (col * row).format!"%2s")
.join(" "))
.join("\n")
].join("\n").writeln;
}
表の大きさを変更できるようにしたい……が
9x9以外にも20x20なども表示できるようにするにはどうしよう
九九の表そのものは、実行時に引数で大きさを渡せば何とでもなりそうだが
問題はformat
の書式指定子の部分がコンパイル時に決まることだ。
自前で必要な表示桁数を計算して、空白を補ってもいいが
ここはD言語らしくコンパイル時に解決する方向で考えよう
4. テンプレート引数
D言語のテンプレート関数には、型名の他に文字列などコンパイル時に決まるものを
テンプレート引数として渡すことができる
これを使って、書式指定子の%2sを%3s、%4sなどに書き換える
void multiplicationTable4(const int E = 9)()
{
import std.stdio;
import std.conv : to;
import std.string : format, join;
import std.range : iota, repeat;
import std.algorithm : map;
enum R = E.to!string.length.to!string; // 行見出しの文字数
enum C = (E ^^ 2).to!string.length.to!string; // 列見出しの文字数
enum S = E * (C.to!int + 1) + R.to!int + 1; // 区切り線の長さ
[
"*".format!("%" ~ R ~ "s |") ~ // * |
iota(1, E + 1) // 1 2 3 4 5 6 7 8 9
.map!(num => num.format!("%" ~ C ~ "s"))
.join(" "),
"-".repeat(S).join, // -----------------------------
iota(1, E + 1) // # |## ## ## ## ## ## ## ## ##
.map!(col => col.format!("%" ~ R ~ "s |") ~
iota(1, E + 1)
.map!(row => (col * row).format!("%" ~ C ~ "s"))
.join(" "))
.join("\n")
].join("\n").writeln;
}
この関数を呼び出すときは
関数名!(テンプレート引数)(実行時引数)
という形式で呼び出すことになる
※実行時引数がない場合、()
は省略できる
void main()
{
multiplicationTable4(); // 9x9
multiplicationTable4!(5)(); // 5x5
multiplicationTable4!(20); // 20x20
multiplicationTable4!39; // 39x39
}
5. CTFE
よく考えたらコンパイル時に表示できそう
auto multiplicationTable5(const int E = 9)()
{
// 前略
return
[
"*".format!("%" ~ R ~ "s |") ~ // * |
// 中略
].join("\n");
}
pragma(msg, multiplicationTable5); // コンパイル時
void main()
{
import std.stdio;
multiplicationTable5.writeln; // 実行時
}
おわり
format部分は、テンプレートmixinにした方が見やすいかも
D言語で書けたのでとりあえず満足
Author And Source
この問題について(D言語で九九表を表示), 我々は、より多くの情報をここで見つけました https://qiita.com/nak2yoshi/items/cd0473989495c8bc4a4c著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .