Qiita Engineer Festa 2024(キータ・エンジニア・フェスタ 2024) - Qiita
において、約1ヶ月で38記事という大量の記事の投稿を要求されることがわかった。
そこで、あまりコストをかけずに記事数を稼ぐ方法を考えた結果、「Welcome to AtCoder を様々な言語で解く」ことを思いついた。
単に解くだけでなく、使用する言語仕様の解説を入れれば、記事として一応成立するだろう。
Welcome to AtCoder
PracticeA - Welcome to AtCoder
Welcome to AtCoder では、以下の形式で整数 $a$, $b$, $c$ および文字列 $s$ が入力として与えられる。
a
b c
s
この入力をもとに、与えられた整数の和 $sum = a + b + c$ および文字列 $s$ を、以下の形式で出力することが求められる。
sum s
整数 $a$, $b$, $c$ は 1 以上 1,000 以下である。
今回用いる Zig の機能
Documentation - The Zig Programming Language
関数定義
fn 関数名(引数リスト) 返り値の型 {
処理内容
}
の形式で関数を定義できる。
return 値;
により、関数から値を返すことができる。
プログラムの実行開始時に実行される関数は
pub fn main() !void {
処理内容
}
の形で定義できる。
型
今回は以下の型を用いる。
型 | 意味 |
---|---|
i32 |
32ビット符号つき整数 |
u32 |
32ビット符号なし整数 |
u8 |
8ビット符号なし整数 |
リテラル
Integer Literals
String Literals and Unicode Code Point Literals
整数のリテラルは、十進数 72
や十六進数 0x123
などを用いることができる。
文字列リテラルは、"hello"
のように "
で囲むことで表現できる。
文字 (文字コード) リテラルは、'a'
のように '
で囲む。
条件分岐
if (条件式) {
条件式が真の場合の処理
} else {
条件式が偽の場合の処理
}
if (条件式) {
条件式が真の場合の処理
}
の形式で、条件分岐ができる。
ループ
while (true) {
処理
}
の形式で、「処理」を無限に繰り返し実行できる。
break;
により、このループから抜けることができる。
Zig では条件を指定してのループや配列の走査 (for) も使えるが、今回は使用しなかった。
定数と変数
const 識別子 = 値;
の形式で、識別子に値を割り当てることができる。(すなわち、定数を定義できる)
var 識別子: 型 = 初期値;
の形式で、変数を定義できる。
変数には以下のように =
を用いて値を代入できる。
識別子 = 値;
定数の定義では型を省略しやすいが、変数の定義では型を明示したほうがよさそうである。
たとえば、以下のように型を省略して整数の変数を定義しようとすると、以下のコンパイルエラーになった。
var value = 0;
error: variable of type 'comptime_int' must be const or comptime
var value = 0;
^~~~~
note: to modify this variable at runtime, it must be given an explicit fixed-size number type
演算子
今回は、以下の演算子を用いた。
演算子 | 意味 |
---|---|
a + b |
加算 |
a - b |
減算 |
a * b |
乗算 |
a == b |
a と b の値が同じかを判定 |
a <= b |
a が b 以下かを判定 |
a and b |
論理AND |
特に、論理ANDはC言語では &&
だが、Zig では and
である。
入出力
Hello World
String Literals and Unicode Code Point Literals
Anonymous List Literals
if
Error Union Type
@bitCast
@truncate
std.io
std.fs.File
std.io.Reader
std.io.Writer
入出力は、標準ライブラリを用いて行うことができる。
以下のようにして、標準ライブラリを std
として読み込める。
const std = @import("std");
以下のようにすると、標準出力に出力する用の Writer
オブジェクト stdout
を取得できる。
const stdout = std.io.getStdOut().writer();
この Writer
の print
メソッドにより、データを出力できる。
try stdout.print(書式指定文字列, .{データリスト});
書式指定文字列では、{d}
と書くとデータ(数値)を十進数で出力でき、{c}
と書くとデータ(8ビットの数値)を文字として出力できる。
C言語の getchar()
が返すように i32
(符号付き32ビット整数) に -1
(EOF) または1バイトを表す 0~255 の整数が格納されているとき、(EOF が入っている可能性は無視して) この1バイトを出力するのは以下のようにできる。
-
@bitCast
により、i32
のデータを同じバイト列で表されるu32
(符号なし32ビット整数) に変換する -
@truncate
により、整数の上位ビットを切り捨ててu8
に変換する - 変換した値を出力する
const ch: i32 = 0x30;
try stdout.print("{c}", .{@truncate(u8, @bitCast(u32, ch))});
@bitCast
および @truncate
は、現在最新の 0.13.0 では引数を1個だけとり、変換先の型は変換結果を使う側が受け入れる型によって決まる。
しかし、AtCoder で用いられる 0.10.1 では引数を2個とり、変換先の型を明示する。
以下のようにすることで、標準入力から1バイト読み込み、数値として取得できる。
fn readChar() i32 {
const stdin = std.io.getStdIn().reader();
const c = stdin.readByte();
if (c) |value| {
return value;
} else |_| {
return -1;
}
}
まず、std.io.getStdIn().reader()
により読み込み用のオブジェクトを取得する。
次に、stdin.readByte()
によりこのオブジェクトを用いて1バイト読み込む。
これは NoEofError!u8
という型のデータを返す。
この型は、エラーのデータと通常のデータが重ね合わせになっているので、if
文を用いて分離する。
提出コード
const std = @import("std");
fn readChar() i32 {
const stdin = std.io.getStdIn().reader();
const c = stdin.readByte();
if (c) |value| {
return value;
} else |_| {
return -1;
}
}
fn readInt() i32 {
var value: i32 = 0;
while (true) {
const c = readChar();
if ('0' <= c and c <= '9') {
value = value * 10 + c - '0';
} else {
return value;
}
}
}
pub fn main() !void {
const stdout = std.io.getStdOut().writer();
const a = readInt();
const b = readInt();
const c = readInt();
try stdout.print("{d} ", .{a + b + c});
while (true) {
const ch = readChar();
try stdout.print("{c}", .{@truncate(u8, @bitCast(u32, ch))});
if (ch == '\n') {
break;
}
}
}