2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AOJ ITP1 を Kotlin で - Getting Started 編

Posted at

はじめに

年が明けて、コーディングの基礎体力を鍛え直したいと思い、AOJ を始めました。

以前、牛尾さんの本で「LeetCode から始めた」という話を読んで、問題を解きながらコーディング力を上げる方法に興味を持っていました。
LeetCode に挑戦したい気持ちはあるものの、英語に抵抗感があったため、まずは日本語で解ける AOJ からスタートすることに。

AOJ の Introduction to Programming I(ITP1)は、入出力や基本的な演算から体系的に学べる構成になっていて、基礎固めにちょうど良さそうです。

この記事では、Getting Started(1_A〜1_D)を Kotlin で解いた記録と、環境で詰まったポイントをまとめます。

動作環境

ローカル環境

この記事で使用している環境は以下の通りです:

  • 開発環境: IntelliJ IDEA 2025.3.1
  • 言語: Kotlin 2.2.20
  • JDK: Amazon Corretto 21.0.9
  • OS: macOS Sequoia 15.7.2

AOJのKotlin環境

AOJのKotlin 環境ではreadln()が使えませんでした。

IntelliJ で書いていると「readLine()readln() に置き換えられます」という警告が出ますが、AOJ では readln() を使うとビルドエラーになります。

これは、readln() が Kotlin 1.6.0 から導入された関数で、AOJ の環境が 1.6.0 より前のバージョンを使っているためと思われます。

解決法

readLine() を使えば問題なく動作します:

fun main() {
    val n = readLine()!!.toInt()
}

readLine()String?(nullable な文字列)を返すため、!!(非 null assertion)を付けて String 型として扱っています。
AOJ では入力が必ず存在するため、!! を使っても問題ありません。

Getting Started

1_A: Hello World

"Hello World" と一行で出力する問題です。

提出したコード

fun main() {
    println("Hello World")
}

1_B: X Cubic

x の 3 乗を計算し結果を出力する問題です。

提出したコード

fun main() {
    val n = readLine()!!.toInt()
    val result = n * n * n
    println(result)
}

1_C: Rectangle

長方形の面積と周囲の長さを計算する問題です。

提出したコード

fun main() {
    val (a, b) = readLine()!!.split(" ").map { it.toInt() }
    val area = a * b
    val perimeter = 2 * (a + b)
    println("$area $perimeter")
}

1_D: Watch

秒を 時間:分:秒 に変換する問題です。

提出したコード

fun main() {
    val S = readLine()!!.toInt()

    val h = S / 3600          // 1時間は3600秒なので、商を求めて「時間」を出す
    val m = (S % 3600) / 60   // 3600で割った余り(1時間に満たない秒数)から「分」を出す
    val s = S % 60            // 最後に60で割った余りが、繰り上がらなかった「秒」

    println("$h:$m:$s")
}

番外編

今回の目的は競プロではなく、Web アプリ開発の基礎体力をつけること。
なので、実務を意識して「読みやすさ」と「変更しやすさ」を重視したコードも書いてみます。

Web アプリ開発を意識した書き方

1_C : Rectangle

/**
 * 長方形のデータとビジネスルールを管理するドメインモデル
 */
data class Rectangle(val width: Int, val height: Int) {
    companion object {
      const val MIN_SIZE = 1
      const val MAX_SIZE = 100 
    }

    init {
      // インスタンス生成時に仕様を満たしているかチェック(ドメインの保護)
      require(width in MIN_SIZE..MAX_SIZE) { "Width must be between $MIN_SIZE and $MAX_SIZE" }
      require(height in MIN_SIZE..MAX_SIZE) { "Height must be between $MIN_SIZE and $MAX_SIZE" }
    }

    // 計算ロジックをプロパティとして隠蔽(外部は計算式を知らなくて良い)
    val area = width * height
    val perimeter = 2 * (width + height)
}

/**
 * 外部の入力(String)をバリデーションし、ドメインモデルへ変換する(変換層)
 */
fun parseRectangle(input: String): Rectangle? {
    val parts = input.trim().split(" ")
    if (parts.size != 2) return null
  
    return try {
      val a = parts[0].toInt()
      val b = parts[1].toInt()
      // 変換と同時にドメインの制約チェックも行う
      Rectangle(a, b)
    } catch (_: Exception) {
      // 数値変換失敗や制約違反を安全に処理
      null
    }
}

/**
 * アプリケーションのエントリーポイント
 */
fun main() {
    // 1. 入力の取得
    val input = readLine() ?: return
  
    // 2. 変換とバリデーション(早期リターンをせず、else でエラーハンドリングを明示)
    val rectangle = parseRectangle(input)
  
    if (rectangle != null) {
      // 3. 正常系の処理:ビジネスロジックの結果を出力
      println("${rectangle.area} ${rectangle.perimeter}")
    } else {
      // 4. 異常系の処理:ユーザーに具体的な修正アクションを提示(UX 向上)
      System.err.println("Error: 「3 5」のように、1〜100 の数値を半角スペース 1 つで区切って入力してください。")
    }
}

なぜこう書くのか:

競プロとしては冗長ですが、「チーム開発で変更に強く、壊れにくいコード」 にするために、以下のWeb開発のプラクティスを取り入れています。

関心の分離(Separation of Concerns)

  • main 関数(司令塔)から「パースの仕方」や「面積の計算式」などの詳細を切り離しました。
  • これにより、main を読むだけで 「入力を取る → モデルに変換する → 結果を出す」という全体の流れが一目で理解できる ようになります。

ドメインモデルによるカプセル化

  • Rectangle クラスにデータと計算ロジックを閉じ込め、外部から計算式を隠蔽しています(DRY 原則)。
  • init ブロックでバリデーションを行い、「不正な状態のオブジェクトは存在すらさせない」 という堅牢な設計を担保しています。

マジックナンバーの排除(保守性の向上)

  • 1100 といった仕様上の数値を定数(MIN_SIZE, MAX_SIZE)として定義しました。
  • これにより、将来の仕様変更時に 「一箇所直すだけで全てのバリデーションとメッセージが更新される」 変更に強い構造になります。

境界(Boundary)の意識と UX

  • 外部からの「汚いデータ」を「綺麗なオブジェクト」に変換する層(parseRectangle)を設けています。
  • 入力ミスに対して 「具体例(3 5)付きのエラーメッセージ」 を返すことで、システムを落とさずにユーザーを正しい操作へ導く実務的な工夫をしています。

テスト容易性(Unit Testability)

  • 「標準入力がなくても、パース関数や計算ロジックが正しいか」を個別に検証できるため、自動テストが書きやすく、大規模開発でも壊れないコードになります。

おわりに

AOJ では入力形式が保証されているため、シンプルなコードで十分ですが、実務では「壊れにくい(想定外の入力にも耐えられる)コード」を意識して書くことが大切だと感じました。

また、Kotlin の data class をドメインモデルとして使うことで、計算ロジックをきれいに整理できることも学べました。

1_D の時間計算は苦手で Gemini に頼ってしまいましたが、ひとまず最初の 4 問を PASS できました🙌

問題に PASS すると Score が表示されるので、なんだか達成感がありますね。

aoj-getting-started.png

コードは GitHub で公開しています。

Here’s to another year of happy coding!✨

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?