以前、完全に独学でこういうことをやった=>ナベアツのプログラムをscalaでクリフトした
今回は人の助けを借りてScalaを学んだので、強くなった僕がもう一度ナベアツクリフトを書けばどう考えても強いはずだと確信した。
要件は前回と同じ。
【7/24修正】スカラとザラキの条件をひっくり返していた&要件を勘違いしていたのを修正
ソースコード
弱いコードと強くなったコードを比べてみる。
弱いほうのコード
import math.pow
object nabeatsu {
def main(args: Array[String]): Unit = {
val num: Int = 40 //この数字まで数える
krift(num)
}
//クリフトする
def krift(num: Int): Unit = {
for (i <- 1 to num) {
//まず3の倍数か調べる
if (i % 3 == 0) println(i + ": スカラ")
//次に,1の桁が3かどうか調べる
else if (i % 10 == 3) println(i + ": ザラキ")
//2桁以上なら,その桁も見る
else if (i >= 10) {
//今見ている数値の,10の桁~最大桁それぞれが3かどうか調べる
import scala.util.control.Breaks.{breakable, break}
breakable {
for (j <- 1 to calcDigit(i) - 1) {
//具体的には10^jで割ることで桁をずらす
//小数点はintにして丸める
if (((i / pow(10, j)) % 10).toInt == 3) {
println(i + ": ザラキ")
break
}
}
}
}
}
}
//数値の桁数を返す再帰的ループ
//具体的には( 10で割った回数 + 1 ) = 桁数
def calcDigit(fig: Int): Int = {
if (fig < 10) 1 //例えば元々10未満なら1桁
else calcDigit(fig / 10) + 1 //10以上なら10で割って再帰
}
}
強いほうのコード
object Words {
val zarakiWord: String = "ザラキ"
val scalaWord: String = "スカラ"
val emptyWord: String = ""
}
case class Krift(turnValue: Int) {
val turn: Int = turnValue
val mahou: String = judge
private[this] def judge: String = turnValue match {
case t if isMod3 => Words.scalaWord
case t if isContained3 => Words.zarakiWord
case _ => Words.emptyWord
}
private[this] def isMod3: Boolean = turnValue % 3 == 0
private[this] def isContained3: Boolean = turnValue.toString.contains("3")
}
object Nabeatsu {
def getKriftList(turnNumber: Int): Seq[Krift] = (1 to turnNumber).map(Krift)
def getScalaTurns(kriftList: Seq[Krift]): Int = kriftList.count(_.mahou == Words.scalaWord)
def getZarakiTurns(kriftList: Seq[Krift]): Int = kriftList.count(_.mahou == Words.zarakiWord)
def getMysteriousTurns(kriftList: Seq[Krift]): Int = kriftList.count(_.mahou == Words.emptyWord)
}
object Main {
def main(args: Array[String]): Unit = {
val turnNumber: Int = 50000
val kriftList: Seq[Krift] = Nabeatsu.getKriftList(turnNumber)
kriftList.foreach(krift => println(s"ターン${krift.turn}:${krift.mahou}"))
println(s"スカラを唱えたターン数:${Nabeatsu.getScalaTurns(kriftList)}")
println(s"ザラキを唱えたターン数:${Nabeatsu.getZarakiTurns(kriftList)}")
println(s"何も唱えない謎のターン数:${Nabeatsu.getMysteriousTurns(kriftList)}")
}
}
どう変わった?
見るからに何らかの思想《おきもち》に染まったようなコードに変化している。
処理の作りと分離
まず、弱いコードはfor文を中心に処理が集中していることに対して、強いコードはクラスやメソッドをだいぶはっきり分けている(と思う)。
Scalaの便利なメソッドを知ったこともあり、処理自体も簡素にまとまっている。妙に複雑なことはきっとしていない。
例えばターン数に3が含まれていることの判定処理は、弱いコードでは再帰処理にmod計算を合わせてたいへんコーディングをしていたが、強いコードでは文字列にしてから3が含まれるか調べておしまいである(.toString.contains)。
クラスやメソッドの利用
また、クラスやケースクラスを習得したため「ターン数と呪文を持つKriftクラスのリストを作り、そのリストに対して処理を行う」という作法ができている。
高階関数mapを利用して「リストの要素それぞれに処理をかける」という作りを実現できたのは強くなった証拠。
修正したらmapがcountに化けた。なんて便利なんだ。
ケースクラスKriftが内部に処理を持っていることが諸説を生みそうだが、ターン番号だけ渡せば勝手にザラキ判定してくれるためそうした。処理の分離や命名規則の観点からはやはり諸説が生成されそう。
結果
5万回試した結果を比較する。
弱いほうの結果
クリフトは16666回スカラを唱えた
クリフトは15837回ザラキを唱えた
強いほうの結果
スカラを唱えたターン数:16666
ザラキを唱えたターン数:15837
何も唱えない謎のターン数:17497
反省
要件を勘違いしていたので、弱いほうのコードは要件を満たしていないと思い込んだ。弱いコードもちゃんと要件は満たしていた。ごめんて。
冷静に考えると、弱いコードでは5万ターン走っているのに、1万ターン以上にわたりザラキでもスカラでもない謎のターンが存在している。
それは正しい要件なのだ。
ちなみに修正前はスカラとザラキの判定をひっくり返してしまっていた&何も唱えない謎のターンがザラキ判定だったのでこういう状態だった
ザラキを唱えたターン数:32503
スカラを唱えたターン数:17497
結論
- 強くなったので要件を満たしつついい感じのコードを書けるようになった
- 自分で書いた要件も読めないクソ雑魚エンジニアになってしまった