序文
Crystalについては資料が少ないので、学習記録を残してみます。
リファレンス的なものについては、公式サイトを参照して下さい。
バージョンは0.33.0です。
最初の題材は、AtCoder ProblemsのBoot camp for Beginnersです。
Apple Siliconにインストールした際の記録は別記事にしました。immatureなので限定公開記事にしています。
Easy_1
求める答えは、$⌈(B-1) / (A-1)⌉$。変な括弧は切り上げ。
メソッドの定義
$⌈x/y⌉$を返すdiv_ceil(x, y)関数を定義する。
x, y ↦ ⌈x/y⌉であるので関数で間違いはないのだが、Crystalでは値を返す返さないに関わらず、全てメソッドと呼ぶ。
div_ceil(x, y)はプログラムそれ自体というグローバルオブジェクト1に関連付けられたメソッド。
Crystalではすべてがオブジェクトとメソッドで出来ている。
def div_ceil(x, y)
(x+y-1) // y
end
メソッドの定義はdef … endで行う。
ここではx, yという引数を受け取って計算し、返り値(オブジェクト)を返している。
引数や返り値の型を明示する必要はない。
整数を与えれば、デフォルトでは符号付き32ビット整数として扱われる。
pythonと違って、インデントに入れ子構造を示す役割はない。
値を返すのにreturn文は不要。
+, -, *, /, %, **はそれぞれは加減乗除、剰余、累乗の計算を行う中置演算子。
ただしCrystalでは演算子/は商を実数(小数型)で返すので、ここでは都合が悪い。
**整数で返す演算子は//**で、商を切り下げて整数型で返す。小数や負数を含む除算でも同じこと(-∞に向かって丸める)。
一文で言うと、x//y = ⌊x/y⌋。
標準入力を受けとる
a, b = read_line.split.map(&.to_i)
標準入力
read_lineは標準入力から1行を読みこむメソッドで、String型で返す。2
文字列の加工
Object#methodはオブジェクトObjectに対応したmethodを示す表記で、実際のコードではinstance.methodの形で書く。
String#splitはString型に対応したsplitメソッド。1つのStringをホワイトスペースで区切って複数のStringに分解し、Array型で返す。
StringとArray
Stringは文字列をUTF-8で保持する。UTF-8は1文字に費やすメモリが不定長(1〜4バイト)なので、ランダムアクセスできない(s[i]は用意されているがiに比例したコストがかかり、時間計算量は$O(|S|)$ )になるので注意が必要。
ただし、要素が全てASCII文字であれば、全て1バイトなので原理上ランダムアクセス可能となり、実際$O(1)$となるように実装されている。
競プロでは$O(1)$と見做してよい。
Arrayは配列を表すデータ構造で、不定長が許される。全ての要素の型が同じ必要があるが、Union型も許される。
マッピングと型変換
to_iはオブジェクトを整数型に変換するメソッドで、map(&.to_i)は配列等の要素一つ一つを整数型に変換する。
構文を調べていくと奥が深い。このまま覚えるのがよい。
奥が深いとは
`Array#map(...)`メソッドは元の配列の要素一つ一つを`()`内のブロックに渡して、そのブロックからの返り値からなる新しい配列を返す。 ブロック`&.to_i`はブロック`{|x| x.to_i}`と等価の表現。 `{|x| x.to_i}`というブロックは一変数を`x`に受け取り、`x.to_i`を返す表現。ここまでのまとめ
標準入力が"4 10"の場合を例にとると、
read_line.split.map(&.to_i)
⇒ "4 10".split.map(&.to_i)
⇒ ["4", "10"].map(&.to_i) ([“4”, “10”]で長さ2の配列)
⇒ ["4".to_i, "10".to_i] (“4”, “10”は文字列)
⇒ [4, 10] (4, 10は数値)
多重代入
ここまででa, b = [4, 10]。
CrystalはPythonのように多重代入が可能。
左辺がコンマ区切り、右辺がIndexable(添字でアクセス可能)の場合にこの構文が可能となり、先頭から順にマッチさせられて代入される。
a, b = [4, 10]
⇒ a = [4, 10][0]; b = [4, 10][1]
⇒ a = 4; b = 10
となる。;を使用すると複数の文を1行で記述できる。
右辺の要素が余った時は、残った要素は使用されない。足りない場合はランタイムエラーとなる。
標準出力
puts div_ceil(b-1, a-1)
求める答えはdiv_ceil(b-1, a-1)の値であるから、これを標準出力する。
putsは引数をStringに変換して標準出力するメソッド。
Crystalではf(x)とf xは同じ。f(x, y)とf x, yは同じ。
つまりputs(div_ceil(b-1, a-1))でもよい。
puts(div_ceil b-1, a-1)でもputs div_ceil b-1, a-1でもよい。深さ優先なのか。
(puts (div_ceil b-1, a-1))と書くとちょっとLispっぽい。
可読性を考えると、返り値が重要なものはf(x)、副作用が重要なものはf xが良いのではなかろうか。
正答例
main関数のような形で書く必要はなく、実行したい処理は地の文に書く。
def div_ceil(x, y)
(x+y-1) // y
end
a, b = read_line.split.map(&.to_i)
puts div_ceil(b-1, a-1)
出来上がり。
メソッド定義はC++の様に必ずしも前置である必要はなく、後置でもいける。
a, b = read_line.split.map(&.to_i)
puts div_ceil(b-1, a-1)
def div_ceil(x, y)
(x+y-1) // y
end