S'entraîner : principes de base de Kotlin

1. Avant de commencer

Maintenant que vous avez travaillé dur pour apprendre les bases de la programmation Kotlin, il est temps de mettre vos connaissances en pratique.

Ces exercices testeront votre compréhension des concepts que vous avez étudiés. Ils sont basés sur des cas d'utilisation concrets. Il est d'ailleurs probable que vous en ayez déjà expérimenté quelques-uns auparavant en tant qu'utilisateur.

Pour trouver une solution à chaque exercice dans Kotlin Playground, suivez les instructions. Si vous êtes bloqué, certains exercices incluent des conseils pour vous aider. Le code de solution est disponible à la fin de chaque exercice, mais nous vous recommandons d'essayer de résoudre les exercices par vous-même avant de vérifier la solution.

Réalisez ces exercices à votre rythme. L'estimation de leur durée est uniquement fournie à titre de référence. Ne suivez pas ces estimations à la lettre. Prenez le temps de résoudre chaque problème de manière réfléchie. Les solutions ne représentent qu'une manière de résoudre les exercices. N'hésitez pas à tester des approches différentes si vous le souhaitez.

Conditions préalables

Ce dont vous avez besoin

  • Kotlin Playground

2. Notifications mobiles

En général, vous recevez un récapitulatif des notifications sur votre téléphone.

Dans le code initial fourni dans l'extrait de code suivant, rédigez un programme qui imprimera le message récapitulatif en fonction du nombre de notifications que vous avez reçues. Ce message devra inclure les éléments suivants :

  • Nombre exact de notifications lorsqu'il ne dépasse pas 100 notifications
  • 99+ pour le nombre de notifications lorsqu'il y en a plus de 100
fun main() {
    val morningNotification = 51
    val eveningNotification = 135

    printNotificationSummary(morningNotification)
    printNotificationSummary(eveningNotification)
}

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

Exécutez la fonction printNotificationSummary() pour que le programme imprime les lignes suivantes :

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

3. Prix du billet de cinéma

Les places de cinéma sont généralement facturées différemment selon l'âge des spectateurs.

Dans le code initial fourni dans l'extrait de code suivant, rédigez un programme qui calculera le prix des billets en fonction de l'âge :

  • Un billet enfant de 15 $ pour les personnes âgées de 12 ans et moins.
  • Un prix standard de 30 $ par billet pour les personnes âgées de 13 à 60 ans. Le lundi, faites passer le prix du billet standard à 25 $ pour cette tranche d'âge.
  • Un billet senior de 20 $ pour les personnes âgées de 61 ans et plus. Nous supposerons que l'âge maximal d'un spectateur est de 100 ans.
  • Une valeur -1 pour indiquer que le prix n'est pas valable lorsqu'un utilisateur saisit une tranche d'âge qui ne correspond pas à celles spécifiées.
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.
}

Exécutez la fonction ticketPrice() pour que le programme imprime les lignes suivantes :

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. Convertisseur de température

Il existe trois échelles de température principales : les degrés Celsius, les degrés Fahrenheit et le kelvin.

Dans le code initial fourni dans l'extrait de code suivant, rédigez un programme qui convertira la température d'une échelle de température à une autre à l'aide des formules suivantes :

  • Degrés Celsius à Fahrenheit : ° F = 9/5 (° C) + 32
  • Kelvin à Celsius : ° = K - 273,15
  • Degrés Fahrenheit à kelvin : K = 5/9 (°F - 32) + 273,15

Notez que la méthode String.format("%.2f", /* measurement */ ) permet de convertir un nombre en type String avec deux décimales.

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.")
}

Exécutez la fonction main() pour qu'elle appelle la fonction printFinalTemperature() et affiche les lignes suivantes. Vous devrez transmettre des arguments pour la formule de température et de conversion. Conseil : Nous vous recommandons d'utiliser des valeurs Double pour éviter la troncation de l'entier (Integer) lors des opérations de division.

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. Catalogue de chansons

Imaginez que vous deviez créer une application de lecture de musique.

Créez une classe pouvant représenter la structure d'une chanson. La classe Song devra inclure les éléments de code suivants :

  • Propriétés de la chanson, artiste, année de publication et nombre de lectures
  • Propriété indiquant si la chanson est populaire (si le nombre de lectures est inférieur à 1 000, considérez-la comme peu populaire)
  • Méthode d'impression d'une description de chanson au format suivant :

"[Titre], interprété par [artiste], est sorti en [année de publication]."

6. Profil Internet

Il arrive souvent de devoir remplir des profils contenant des champs obligatoires et non obligatoires sur certains sites Web. Par exemple, vous pouvez ajouter vos informations personnelles et créer un lien vers les tiers qui vous ont conseillé de créer ce profil.

Dans le code initial fourni dans l'extrait de code suivant, rédigez un programme qui imprimera les détails du profil d'une personne.

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
    }
}

Exécutez la fonction showProfile() pour que le programme imprime les lignes suivantes :

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. Téléphones pliables

En général, l'écran d'un téléphone s'allume et s'éteint lorsque l'utilisateur appuie sur le bouton Marche/Arrêt. En revanche, si un téléphone pliable est plié, son écran interne principal ne s'allume pas lorsque vous appuyez sur ce bouton.

Dans le code initial fourni dans l'extrait de code suivant, écrivez une classe FoldablePhone qui héritera de la classe Phone. Elle doit contenir les éléments suivants :

  • Une propriété qui indique si le téléphone est plié
  • Un comportement de la fonction switchOn() différent de celui de la classe Phone pour que l'écran ne s'allume que lorsque le téléphone n'est pas plié
  • Des méthodes permettant de modifier l'état du pliage
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. Enchère spéciale

Lors d'une mise aux enchères, l'enchère la plus élevée détermine généralement le prix d'un article. Dans cette enchère spéciale, si personne n'enchérit pour un objet, celui-ci est automatiquement cédé à l'hôtel des ventes au prix minimum.

Dans le code initial fourni dans l'extrait de code suivant, une fonction auctionPrice() (qui accepte un type Bid? pouvant avoir une valeur nulle en tant qu'argument) vous est fournie :

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.
}

Exécutez la fonction auctionPrice() pour que le programme imprime les lignes suivantes :

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

9. Code de solution

Notifications mobiles

La solution utilise une instruction if/else pour imprimer le message de notification approprié en fonction du nombre de messages de notification reçus :

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.")
    }
}

Prix du billet de cinéma

La solution utilise une expression when pour renvoyer le prix approprié du billet en fonction de l'âge du spectateur. Elle utilise également une expression if/else simple pour l'une des branches de l'expression when afin d'ajouter la condition supplémentaire dans la tarification standard des billets.

Le prix du billet dans la branche else renvoie une valeur -1, qui indique que l'ensemble de prix n'est pas valide pour la branche else. Une implémentation plus efficace consiste à renvoyer une exception pour la branche else. Vous vous familiariserez davantage avec le traitement des exceptions dans les prochains modules.

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
    }
}

Convertisseur de température

La solution nécessite de transmettre une fonction en tant que paramètre à la fonction printFinalTemperature(). La solution la plus succincte transmet les expressions lambda en tant qu'arguments, utilise la référence de paramètre it à la place des noms de paramètre et exploite la syntaxe lambda de fin.

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.")
}

Catalogue de chansons

La solution contient une classe Song avec un constructeur par défaut qui accepte tous les paramètres obligatoires. La classe Song comporte également une propriété isPopular qui utilise une fonction getter personnalisée et une méthode qui imprime la description d'elle-même. Vous pouvez créer une instance de cette classe dans la fonction main() et appeler ses méthodes pour vérifier si l'implémentation est correcte. Vous pouvez aussi utiliser des traits de soulignement lors de l'écriture des grands nombres, tels que la valeur 1_000_000, pour améliorer la lisibilité.

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.")
    }
}

Lorsque vous appelez la fonction println() au niveau des méthodes de l'instance, le programme peut afficher cette sortie :

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

Profil Internet

La solution contient des vérifications de valeur nulle dans différentes instructions if/else pour imprimer différents textes selon que les propriétés de classe sont null ou non :

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")
    }
}

Téléphones pliables

Pour que la classe Phone soit une classe parent, vous devez l'ouvrir en ajoutant le mot clé open avant son nom. Pour remplacer la méthode switchOn() dans la classe FoldablePhone, vous devez rendre la méthode située dans la classe Phone accessible en ajoutant le mot clé open avant la méthode.

La solution contient une classe FoldablePhone avec un constructeur par défaut qui inclut un argument par défaut pour le paramètre isFolded. La classe FoldablePhone permet également de remplacer la propriété isFolded par une valeur true ou false. Elle remplace également la méthode switchOn() héritée de la classe Phone.

Vous pouvez créer une instance de cette classe dans la fonction main() et appeler ses méthodes pour vérifier si l'implémentation est correcte.

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()
}

La sortie est la suivante :

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

Enchère spéciale

La solution utilise l'opérateur d'appel sécurisé ?. et l'opérateur Elvis ?: pour renvoyer le prix correct :

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. Exercices supplémentaires

Pour vous entraîner davantage en langage Kotlin, consultez le cours sur les bases du langage Kotlin, proposé par JetBrains Academy. Consultez la fiche info afin d'accéder à la liste des sujets abordés dans ce cours et de choisir celui qui vous intéresse.