演習: Kotlin の基礎

1. 始める前に

Kotlin プログラミングの基礎を学んだので、次は学んだことを実践しましょう。

この演習では、学習したコンセプトの理解度をテストします。実際のユースケースに基づいており、一部はユーザーとして以前に経験したことがあるかもしれません。

手順に沿って、Kotlin のプレイグラウンドで各問題を解きましょう。一部の問題には、行き詰まった場合に役立つヒントが用意されています。各問題の解答コードは最後にありますが、解答を確認する前に自分で考えることをおすすめします。

自分のペースで進めてください。それぞれの問題には所要時間を示していますが、目安にすぎませんので、それにとらわれる必要はありません。必要なだけ時間をかけ、じっくりと問題に取り組んでください。回答例はあくまでも一つの例ですので、納得がいくまで試してください。

前提条件

必要なもの

  • Kotlin のプレイグラウンド

2. モバイル通知

通常、スマートフォンには通知の概要が表示されます。

以下のコード スニペットの初期コードの中に、受信した通知数に基づいて概要メッセージを出力するプログラムを書いてください。メッセージには次の内容を入れてください。

  • 通知数が 100 未満の場合は、正確な通知数。
  • 通知数が 100 以上の場合は、99+
fun main() {
    val morningNotification = 51
    val eveningNotification = 135

    printNotificationSummary(morningNotification)
    printNotificationSummary(eveningNotification)
}

fun printNotificationSummary(numberOfMessages: Int) {
    // Fill in the code.
}

printNotificationSummary() 関数を完成させて、プログラムが以下の行を出力するようにしてください。

You have 51 notifications.
Your phone is blowing up! You have 99+ notifications.

3. 映画のチケット料金

映画のチケット料金は、通常、映画を観る人の年齢に応じて異なります。

以下のコード スニペットの初期コードの中に、次のような年齢に基づいたチケット料金を計算するプログラムを書いてください。

  • 12 歳以下は、15 ドルの子供料金。
  • 13 歳から 60 歳までは、30 ドルの一般料金(月曜日は 25 ドルの割引料金)。
  • 61 歳以上は、20 ドルのシニア料金(最高齢は 100 歳と想定)。
  • 想定外の年齢が入力された場合は、無効な料金を表す値 -1
fun main() {
    val child = 5
    val adult = 28
    val senior = 87

    val isMonday = true

    println("The movie ticket price for a person aged $child is  \$${ticketPrice(child, isMonday)}.")
    println("The movie ticket price for a person aged $adult is \$${ticketPrice(adult, isMonday)}.")
    println("The movie ticket price for a person aged $senior is \$${ticketPrice(senior, isMonday)}.")
}

fun ticketPrice(age: Int, isMonday: Boolean): Int {
    // Fill in the code.
}

ticketPrice() 関数を完成させて、プログラムが以下の行を出力するようにしてください。

The movie ticket price for a person aged 5 is $15.
The movie ticket price for a person aged 28 is $25.
The movie ticket price for a person aged 87 is $20.

4. 温度の変換

世界で使用されている主な温度の単位は、摂氏、華氏、ケルビンの 3 つです。

以下のコード スニペットの初期コードの中に、温度をある単位から別の単位に変換するプログラムを書いてください。

  • 摂氏(C)から華氏(F): F = 9/5 (° C) + 32
  • ケルビン(K)から摂氏(C): C = K - 273.15
  • 華氏(F)からケルビン(K): K = 5/9 (° F - 32) + 273.15

String.format("%.2f", /* measurement */ ) メソッドを使用して、数値を小数点以下 2 桁までの String 型に変換します。

fun main() {
    // Fill in the code.
}

fun printFinalTemperature(
    initialMeasurement: Double,
    initialUnit: String,
    finalUnit: String,
    conversionFormula: (Double) -> Double
) {
    val finalMeasurement = String.format("%.2f", conversionFormula(initialMeasurement)) // two decimal places
    println("$initialMeasurement degrees $initialUnit is $finalMeasurement degrees $finalUnit.")
}

main() 関数を完成させ、printFinalTemperature() 関数を呼び出して、以下の行を出力するようにしてください。必ず、温度と変換式を引数として渡してください。

27.0 degrees Celsius is 80.60 degrees Fahrenheit.
350.0 degrees Kelvin is 76.85 degrees Celsius.
10.0 degrees Fahrenheit is 260.93 degrees Kelvin.

5. 楽曲カタログ

音楽プレーヤー アプリを作成することを考えます。

曲の構造を表すクラスを作成しましょう。Song クラスには、以下のコード要素を入れてください。

  • タイトル、アーティスト、リリース年、再生回数のプロパティ
  • 曲が人気があるかどうかを表すプロパティ(再生回数が 1,000 未満の場合は人気がないと見なす)
  • 次の形式で曲の説明を出力するメソッド:

"[タイトル], performed by [アーティスト], was released in [リリース年]."

6. インターネット プロフィール

オンライン ウェブサイトで、必須の項目と必須でない項目があるプロフィールの入力を求められることがよくあります。たとえば、プロフィールを作るために個人情報や紹介者へのリンクを記入するなどします。

以下のコード スニペットの初期コードの中に、個人のプロフィール情報を出力するプログラムを書いてください。

fun main() {
    val amanda = Person("Amanda", 33, "play tennis", null)
    val atiqah = Person("Atiqah", 28, "climb", amanda)

    amanda.showProfile()
    atiqah.showProfile()
}

class Person(val name: String, val age: Int, val hobby: String?, val referrer: Person?) {
    fun showProfile() {
       // Fill in code
    }
}

showProfile() 関数を完成させて、プログラムが以下の行を出力するようにしてください。

Name: Amanda
Age: 33
Likes to play tennis. Doesn't have a referrer.

Name: Atiqah
Age: 28
Likes to climb. Has a referrer named Amanda, who likes to play tennis.

7. 折りたたみ式スマートフォン

通常、スマートフォンの画面は、電源ボタンを押すとオンとオフが切り替わります。一方、折りたたみ式スマートフォンを折りたたんだ場合、メインの内部画面は、電源ボタンを押したときにオンになりません。

以下のコード スニペットの初期コードの中に、Phone クラスを継承する FoldablePhone クラスを書いてください。そのクラスには、以下のものを含めてください。

  • スマートフォンが折りたたまれているかどうかを示すプロパティ。
  • スマートフォンが折りたたまれていないときにだけ画面をオンにする、Phone クラスとは異なる動作の switchOn() 関数。
  • 折りたたみ状態を変更するメソッド。
class Phone(var isScreenLightOn: Boolean = false){
    fun switchOn() {
        isScreenLightOn = true
    }

    fun switchOff() {
        isScreenLightOn = false
    }

    fun checkPhoneScreenLight() {
        val phoneScreenLight = if(isScreenLightOn) "on" else "off"
        println("The phone screen's light is $phoneScreenLight.")
    }
}

8. 特殊なオークション

通常のオークションでは、最高額入札者がアイテムの価格を決定します。このオークションでは、入札者がいない場合、自動的に最低価格でオークション会社に売却されます。

以下のコード スニペットの初期コードの中に、null 値許容の Bid? 型を引数として受け取る auctionPrice() 関数を用意してあります。

fun main() {
    val winningBid = Bid(5000, "Private Collector")

    println("Item A is sold at ${auctionPrice(winningBid, 2000)}.")
    println("Item B is sold at ${auctionPrice(null, 3000)}.")
}

class Bid(val amount: Int, val bidder: String)

fun auctionPrice(bid: Bid?, minimumPrice: Int): Int {
   // Fill in the code.
}

auctionPrice() 関数を完成させて、プログラムが以下の行を出力するようにしてください。

Item A is sold at 5000.
Item B is sold at 3000.

9. 解答コード

モバイル通知

この解答では、if/else 文を使い、受け取った通知メッセージの数に基づいて、適切な通知概要のメッセージを出力します。

fun main() {
    val morningNotification = 51
    val eveningNotification = 135

    printNotificationSummary(morningNotification)
    printNotificationSummary(eveningNotification)
}

fun printNotificationSummary(numberOfMessages: Int) {
    if (numberOfMessages < 100) {
        println("You have ${numberOfMessages} notifications.")
    } else {
        println("Your phone is blowing up! You have 99+ notifications.")
    }
}

映画のチケット料金

この解答では、when 式を使い、年齢に基づいて適切なチケット料金を返しています。また、when 式の分岐の一つで簡単な if/else 式を使い、一般チケット料金に条件を追加しています。

else の分岐では、チケット料金として else 分岐の価格設定が無効であることを示す値 -1 を返します。この else の分岐では例外をスローする方が良い実装なのですが、例外処理は今後のユニットで学習します。

fun main() {
    val child = 5
    val adult = 28
    val senior = 87

    val isMonday = true

    println("The movie ticket price for a person aged $child is  \$${ticketPrice(child, isMonday)}.")
    println("The movie ticket price for a person aged $adult is \$${ticketPrice(adult, isMonday)}.")
    println("The movie ticket price for a person aged $senior is \$${ticketPrice(senior, isMonday)}.")
}

fun ticketPrice(age: Int, isMonday: Boolean): Int {
    return when(age) {
        in 0..12 -> 15
        in 13..60 -> if (isMonday) 25 else 30
        in 61..100 -> 20
        else -> -1
    }
}

温度の変換

この解答では、printFinalTemperature() 関数のパラメータとして、関数を渡す必要があります。ラムダ式を引数として渡す最も簡潔な方法は、パラメータ名に it パラメータ参照を使い、末尾ラムダ構文を使う方法です。

fun main() {
        printFinalTemperature(27.0, "Celsius", "Fahrenheit") { 9.0 / 5.0 * it + 32 }
        printFinalTemperature(350.0, "Kelvin", "Celsius") { it - 273.15 }
        printFinalTemperature(10.0, "Fahrenheit", "Kelvin") { 5.0 / 9.0 * (it - 32) + 273.15 }
}

fun printFinalTemperature(
    initialMeasurement: Double,
    initialUnit: String,
    finalUnit: String,
    conversionFormula: (Double) -> Double
) {
    val finalMeasurement = String.format("%.2f", conversionFormula(initialMeasurement)) // two decimal places
    println("$initialMeasurement degrees $initialUnit is $finalMeasurement degrees $finalUnit.")
}

楽曲カタログ

この解答には、必要なすべてのパラメータを受け取るデフォルト コンストラクタを持った Song クラスがあります。この Song クラスには、カスタムのゲッター関数を使用する isPopular プロパティと、自身の説明を出力するメソッドがあります。main() 関数の中でこのクラスのインスタンスを作成し、そのメソッドを呼び出すと、実装が正しいかどうかをテストできます。大きな数値を書くときには 1_000_000 のように下線を使って読みやすくできます。

fun main() {
    val brunoSong = Song("We Don't Talk About Bruno", "Encanto Cast", 2022, 1_000_000)
    brunoSong.printDescription()
    println(brunoSong.isPopular)
}

class Song(
    val title: String,
    val artist: String,
    val yearPublished: Int,
    val playCount: Int
){
    val isPopular: Boolean
        get() = playCount >= 1000

    fun printDescription() {
        println("$title, performed by $artist, was released in $yearPublished.")
    }
}

このインスタンスのメソッドで println() 関数を呼び出すと、プログラムは次のように出力します。

We Don't Talk About Bruno, performed by Encanto Cast, was released in 2022.
true

インターネット プロフィール

この解答では、いろいろな if/else 文に null チェックを入れ、各クラス プロパティが null かどうかで異なるテキストを出力するようにしています。

fun main() {
    val amanda = Person("Amanda", 33, "play tennis", null)
    val atiqah = Person("Atiqah", 28, "climb", amanda)

    amanda.showProfile()
    atiqah.showProfile()
}

class Person(val name: String, val age: Int, val hobby: String?, val referrer: Person?) {
    fun showProfile() {
        println("Name: $name")
        println("Age: $age")
        if(hobby != null) {
            print("Likes to $hobby. ")
        }
        if(referrer != null) {
            print("Has a referrer named ${referrer.name}")
            if(referrer.hobby != null) {
                print(", who likes to ${referrer.hobby}.")
            } else {
                print(".")
            }
        } else {
            print("Doesn't have a referrer.")
        }
        print("\n\n")
    }
}

折りたたみ式スマートフォン

Phone クラスを親クラスにするには、クラス名の前に open というキーワードを追加してクラスをオープンする必要があります。また、FoldablePhone クラスで switchOn() をオーバーライドするには、Phone クラスでそのメソッドをオープンにするために、メソッドの前に open キーワードを追加する必要があります。

この解答には、デフォルト コンストラクタを持つ FoldablePhone クラスがあり、そのデフォルト コンストラクタには、デフォルト引数付きの isFolded パラメータがあります。また、FoldablePhone クラスには、isFolded プロパティの値を true にするメソッドと false にするメソッドがあります。さらに、Phone クラスから継承した switchOn() メソッドをオーバーライドしています。

main() 関数の中でこのクラスのインスタンスを作成し、そのメソッドを呼び出すと、実装が正しいかどうかをテストできます。

open class Phone(var isScreenLightOn: Boolean = false){
    open fun switchOn() {
        isScreenLightOn = true
    }

    fun switchOff() {
        isScreenLightOn = false
    }

    fun checkPhoneScreenLight() {
        val phoneScreenLight = if(isScreenLightOn) "on" else "off"
        println("The phone screen's light is $phoneScreenLight.")
    }
}

class FoldablePhone(var isFolded: Boolean = true): Phone() {
    override fun switchOn() {
        if (!isFolded) {
            isScreenLightOn = true
        }
    }

    fun fold() {
        isFolded = true
    }

    fun unfold() {
        isFolded = false
    }
}

fun main() {
    val newFoldablePhone = FoldablePhone()

    newFoldablePhone.switchOn()
    newFoldablePhone.checkPhoneScreenLight()
    newFoldablePhone.unfold()
    newFoldablePhone.switchOn()
    newFoldablePhone.checkPhoneScreenLight()
}

次のような出力が表示されます。

The phone screen's light is off.
The phone screen's light is on.

特殊なオークション

この解答では、セーフコール演算子とエルビス演算子(?.)を使って、正しい価格を返しています。

fun main() {
    val winningBid = Bid(5000, "Private Collector")

    println("Item A is sold at ${auctionPrice(winningBid, 2000)}.")
    println("Item B is sold at ${auctionPrice(null, 3000)}.")
}

class Bid(val amount: Int, val bidder: String)

fun auctionPrice(bid: Bid?, minimumPrice: Int): Int {
    return bid?.amount ?: minimumPrice
}

10. その他の演習

Kotlin 言語に関するその他の演習については、JetBrains Academy の Kotlin 基本トラック(料金不要)をご覧ください。特定のトピックに移動するには、ナレッジマップに移動して、このトラックで扱っているトピックの一覧を確認してください。