1. Introduction
Dans l'atelier de programmation Utiliser les types de fonction et les expressions lambda en Kotlin, vous avez découvert les fonctions d'ordre supérieur, c'est-à-dire celles qui utilisent d'autres fonctions en tant que paramètres et/ou renvoient une fonction, telle que repeat()
. Les fonctions d'ordre supérieur sont particulièrement pertinentes pour les collections, car elles vous permettent d'effectuer des tâches courantes, comme le tri ou le filtrage, en utilisant moins de code. Maintenant que vous disposez d'une base solide pour travailler avec des collections, il est temps de s'intéresser de plus près aux fonctions d'ordre supérieur.
Dans cet atelier de programmation, vous allez découvrir plusieurs fonctions utilisables sur les types de collection, notamment forEach()
, map()
, filter()
, groupBy()
, fold()
et sortedBy()
. Tout au long du processus, vous vous entraînerez également à utiliser les expressions lambda.
Conditions préalables
- Bonne connaissance des types de fonctions et des expressions lambda.
- Bonne connaissance de la syntaxe des lambdas de fin, comme la fonction
repeat()
. - Connaissance des différents types de collections en Kotlin, tels que
List
.
Points abordés
- Comment intégrer des expressions lambda dans des chaînes.
- Comment utiliser différentes fonctions d'ordre supérieur avec la collection
List
, notammentforEach()
,map()
,filter()
,groupBy()
,fold()
etsortedBy()
.
Ce dont vous avez besoin
- Un navigateur Web ayant accès à Kotlin Playground
2. forEach() et modèles de chaîne avec lambdas
Code de démarrage
Dans les exemples suivants, vous allez utiliser une collection List
représentant le menu des cookies d'une boulangerie (de quoi vous donner l'eau à la bouche !), et utiliser des fonctions d'ordre supérieur pour le mettre en forme de différentes manières.
Commencez par configurer le code de démarrage.
- Accédez à Kotlin Playground.
- Au-dessus de la fonction
main()
, ajoutez la classeCookie
. Chaque instance deCookie
représente un élément du menu, avec unname
(nom), unprice
(prix) et d'autres informations sur le cookie.
class Cookie(
val name: String,
val softBaked: Boolean,
val hasFilling: Boolean,
val price: Double
)
fun main() {
}
- Sous la classe
Cookie
, en dehors demain()
, créez une liste de cookies comme indiqué. Le type est supposé êtreList<Cookie>
.
class Cookie(
val name: String,
val softBaked: Boolean,
val hasFilling: Boolean,
val price: Double
)
val cookies = listOf(
Cookie(
name = "Chocolate Chip",
softBaked = false,
hasFilling = false,
price = 1.69
),
Cookie(
name = "Banana Walnut",
softBaked = true,
hasFilling = false,
price = 1.49
),
Cookie(
name = "Vanilla Creme",
softBaked = false,
hasFilling = true,
price = 1.59
),
Cookie(
name = "Chocolate Peanut Butter",
softBaked = false,
hasFilling = true,
price = 1.49
),
Cookie(
name = "Snickerdoodle",
softBaked = true,
hasFilling = false,
price = 1.39
),
Cookie(
name = "Blueberry Tart",
softBaked = true,
hasFilling = true,
price = 1.79
),
Cookie(
name = "Sugar and Sprinkles",
softBaked = false,
hasFilling = false,
price = 1.39
)
)
fun main() {
}
Faire une boucle sur une liste avec forEach()
La première fonction d'ordre supérieur que vous connaissez est forEach()
. La fonction forEach()
exécute une seule fois la fonction transmise en tant que paramètre pour chaque élément de la collection. Le fonctionnement est semblable à celui de la fonction repeat()
, ou à celui d'une boucle for
. Le lambda est exécuté pour le premier élément, puis pour le deuxième, et ainsi de suite pour chaque élément de la collection. Le prototype de la méthode est le suivant :
forEach(action: (T) -> Unit)
forEach()
n'accepte qu'un seul paramètre d'action, à savoir une fonction de type (T) -> Unit
.
T
correspond au type de données que contient la collection. Étant donné que le lambda n'utilise qu'un seul paramètre, vous pouvez omettre son nom et y faire référence avec it
.
Utilisez la fonction forEach()
pour afficher les éléments de la liste cookies
.
- Dans
main()
, appelezforEach()
sur la listecookies
en utilisant la syntaxe d'un lambda de fin. Étant donné que le lambda de fin est le seul argument, vous pouvez omettre les parenthèses lorsque vous appelez la fonction.
fun main() {
cookies.forEach {
}
}
- Dans le corps du lambda, ajoutez une instruction
println()
qui afficheit
.
fun main() {
cookies.forEach {
println("Menu item: $it")
}
}
- Exécutez votre code et observez le résultat. Le nom du type (
Cookie
) et un identifiant unique de l'objet s'affichent, mais pas le contenu de l'objet.
Menu item: Cookie@5a10411 Menu item: Cookie@68de145 Menu item: Cookie@27fa135a Menu item: Cookie@46f7f36a Menu item: Cookie@421faab1 Menu item: Cookie@2b71fc7e Menu item: Cookie@5ce65a89
Intégrer des expressions dans des chaînes
Lorsque vous avez découvert les modèles de chaîne, vous avez vu comment utiliser le caractère dollar ($
) avec un nom de variable pour l'insérer dans une chaîne. Toutefois, cette technique ne fonctionne pas comme prévu lorsqu'elle est associée à l'opérateur point (.
) pour accéder aux propriétés.
- Dans l'appel à
forEach()
, modifiez le corps du lambda pour insérer$it.name
dans la chaîne.
cookies.forEach {
println("Menu item: $it.name")
}
- Exécutez votre code. Notez que nom de la classe,
Cookie
, est inséré, ainsi qu'un identifiant unique pour l'objet suivi de.name
. Le code n'accède pas à la valeur de la propriéténame
.
Menu item: Cookie@5a10411.name Menu item: Cookie@68de145.name Menu item: Cookie@27fa135a.name Menu item: Cookie@46f7f36a.name Menu item: Cookie@421faab1.name Menu item: Cookie@2b71fc7e.name Menu item: Cookie@5ce65a89.name
Pour accéder aux propriétés et les intégrer dans une chaîne, vous avez besoin d'une expression. Vous pouvez insérer une expression dans un modèle de chaîne en l'entourant d'accolades.
L'expression lambda est placée entre accolades. Vous pouvez accéder aux propriétés, effectuer des opérations mathématiques, appeler des fonctions, etc., et la valeur renvoyée par le lambda est insérée dans la chaîne.
Modifions le code pour que le nom soit inséré dans la chaîne.
- Entourez
it.name
d'accolades pour en faire une expression lambda.
cookies.forEach {
println("Menu item: ${it.name}")
}
- Exécutez votre code. La sortie contient le
name
(nom) de chaqueCookie
.
Menu item: Chocolate Chip Menu item: Banana Walnut Menu item: Vanilla Creme Menu item: Chocolate Peanut Butter Menu item: Snickerdoodle Menu item: Blueberry Tart Menu item: Sugar and Sprinkles
3. map()
La fonction map()
vous permet de transformer une collection en une nouvelle collection avec le même nombre d'éléments. Par exemple, map()
peut transformer List<Cookie>
en List<String>
ne contenant que le name
(nom) du cookie, à condition que vous indiquiez à la fonction map()
comment créer une String
(chaîne) à partir de chaque élément Cookie
.
Imaginons que vous écrivez une application qui affiche un menu interactif pour une boulangerie. Lorsque l'utilisateur accède à l'écran qui affiche le menu des cookies, il est probable qu'il souhaite voir les données présentées de manière logique, comme le nom suivi du prix. Vous pouvez créer une liste de chaînes, formatées avec les données appropriées (nom et prix) à l'aide de la fonction map()
.
- Supprimez tout le code précédent de
main()
. Créez une variable appeléefullMenu
et définissez-la sur le résultat de l'appel demap()
sur la listecookies
.
val fullMenu = cookies.map {
}
- Dans le corps du lambda, ajoutez une chaîne formatée pour inclure le
name
(nom) et leprice
(prix) deit
(le cookie).
val fullMenu = cookies.map {
"${it.name} - $${it.price}"
}
- Affichez le contenu de
fullMenu
. Pour ce faire, vous pouvez utiliserforEach()
. La collectionfullMenu
renvoyée parmap()
est de typeList<String>
au lieu deList<Cookie>
. ChaqueCookie
danscookies
correspond à uneString
dansfullMenu
.
println("Full menu:")
fullMenu.forEach {
println(it)
}
- Exécutez votre code. La sortie correspond au contenu de la liste
fullMenu
.
Full menu: Chocolate Chip - $1.69 Banana Walnut - $1.49 Vanilla Creme - $1.59 Chocolate Peanut Butter - $1.49 Snickerdoodle - $1.39 Blueberry Tart - $1.79 Sugar and Sprinkles - $1.39
4. filter()
La fonction filter()
vous permet de créer un sous-ensemble dans une collection. Par exemple, si vous avez une liste de nombres, vous pouvez utiliser filter()
pour créer une liste contenant uniquement les nombres pairs.
Alors que le résultat de la fonction map()
génère toujours une collection de même taille, filter()
peut également générer une collection plus petite que celle d'origine. Contrairement à map()
, la collection finale possède également le même type de données. Par conséquent, filtrer une List<Cookie>
crée une autre List<Cookie>
.
Comme map()
et forEach()
, filter()
utilise une seule expression lambda comme paramètre. Le lambda possède un seul paramètre représentant chaque élément de la collection et renvoie une valeur Boolean
.
Pour chaque élément de la collection :
- Si le résultat de l'expression lambda est
true
, l'élément est inclus dans la nouvelle collection. - Si le résultat est
false
, l'élément n'est pas inclus dans la nouvelle collection.
Cette technique est utile si vous souhaitez obtenir un sous-ensemble de données dans votre application. Par exemple, imaginons que la boulangerie souhaite mettre en avant ses cookies moelleux dans une section distincte du menu. Vous pouvez commencer par utiliser filter()
sur la liste cookies
, puis afficher les éléments.
- Dans
main()
, créez une variable appeléesoftBakedMenu
et définissez-la sur le résultat de l'appel defilter()
sur la listecookies
.
val softBakedMenu = cookies.filter {
}
- Dans le corps du lambda, ajoutez une expression booléenne pour vérifier que la propriété
softBaked
du cookie est égale àtrue
. CommesoftBaked
est unBoolean
lui-même, le corps du lambda ne doit contenir queit.softBaked
.
val softBakedMenu = cookies.filter {
it.softBaked
}
- Affichez le contenu de
softBakedMenu
à l'aide deforEach()
.
println("Soft cookies:")
softBakedMenu.forEach {
println("${it.name} - $${it.price}")
}
- Exécutez votre code. Le menu s'affiche comme auparavant, mais il ne comprend que les cookies moelleux.
... Soft cookies: Banana Walnut - $1.49 Snickerdoodle - $1.39 Blueberry Tart - $1.79
5. groupBy()
La fonction groupBy()
permet de transformer une liste en mappage. Chaque valeur renvoyée unique de la fonction devient une clé du mappage obtenu. Les valeurs de chaque clé correspondent à tous les éléments de la collection qui ont produit cette valeur renvoyée unique.
Le type de données des clés est identique au type renvoyé par la fonction transmise dans groupBy()
. Le type de données de ces valeurs correspond à la liste des éléments d'origine.
Ce concept peut être difficile à comprendre, donc prenons un exemple simple. À partir de la même liste qu'avant, groupez les nombres par valeurs paires et impaires.
Vous pouvez vérifier si un nombre est pair ou impair en le divisant par 2
, puis en observant si le reste après division est 0
ou 1
. Si le reste est 0
, le nombre est pair. Dans le cas contraire, si le reste est 1
, le nombre est impair.
Pour ce faire, vous pouvez utiliser l'opérateur modulo (%
), qui divise la valeur de gauche (le dividende) par la valeur de droite (le diviseur).
Au lieu de renvoyer le résultat de la division, comme c'est le cas avec l'opérateur de division (/
), l'opérateur modulo renvoie le reste après division, ce qui permet par exemple de vérifier si un nombre est pair ou impair.
La fonction groupBy()
est appelée avec l'expression lambda suivante : { it % 2 }
.
Le mappage obtenu comprend deux clés : 0
et 1
. Chaque clé a une valeur de type List<Int>
. La liste de la clé 0
contient tous les nombres pairs et la liste de la clé 1
contient tous les nombres impairs.
Un cas d'utilisation concret peut être une application de photos qui regroupe les clichés en fonction du sujet ou du lieu de prise de vue. Pour notre menu de boulangerie, nous allons regrouper les éléments pour différencier les cookies moelleux des autres.
Utilisez groupBy()
pour réorganiser le menu en fonction de la propriété softBaked
.
- Supprimez l'appel à
filter()
du code de l'étape précédente.
Code à supprimer
val softBakedMenu = cookies.filter {
it.softBaked
}
println("Soft cookies:")
softBakedMenu.forEach {
println("${it.name} - $${it.price}")
}
- Appelez
groupBy()
sur la listecookies
et stockez le résultat dans une variable appeléegroupedMenu
.
val groupedMenu = cookies.groupBy {}
- Transmettez une expression lambda qui renvoie
it.softBaked
. Le type renvoyé estMap<Boolean, List<Cookie>>
.
val groupedMenu = cookies.groupBy { it.softBaked }
- Créez une variable
softBakedMenu
contenant la valeur degroupedMenu[true]
et une variablecrunchyMenu
contenant la valeur degroupedMenu[false]
. Étant donné que le résultat de la mise en indice d'uneMap
peut être nul, vous pouvez utiliser l'opérateur Elvis (?:
) pour renvoyer une liste vide.
val softBakedMenu = groupedMenu[true] ?: listOf()
val crunchyMenu = groupedMenu[false] ?: listOf()
- Ajoutez du code pour afficher le menu des cookies moelleux suivi de celui des cookies croustillants.
println("Soft cookies:")
softBakedMenu.forEach {
println("${it.name} - $${it.price}")
}
println("Crunchy cookies:")
crunchyMenu.forEach {
println("${it.name} - $${it.price}")
}
- Exécutez votre code. En utilisant la fonction
groupBy()
, la liste est divisée en deux, en fonction de la valeur de l'une des propriétés.
... Soft cookies: Banana Walnut - $1.49 Snickerdoodle - $1.39 Blueberry Tart - $1.79 Crunchy cookies: Chocolate Chip - $1.69 Vanilla Creme - $1.59 Chocolate Peanut Butter - $1.49 Sugar and Sprinkles - $1.39
6. fold()
La fonction fold()
permet de générer une valeur unique à partir d'une collection. Elle est principalement utilisée pour calculer un prix total ou pour additionner tous les éléments d'une liste afin de calculer une moyenne.
La fonction fold()
nécessite deux paramètres :
- La valeur initiale. Le type de données est fixé lors de l'appel de la fonction (par exemple, une valeur initiale de
0
fixe le type de valeur surInt
). - Une expression lambda qui renvoie une valeur du même type que la valeur initiale.
L'expression lambda comporte également deux paramètres :
- Le premier est l'accumulateur. Il a le même type de données que la valeur initiale. Voyez cela comme un total cumulé. Chaque fois que le lambda est appelé, l'accumulateur est égal à la valeur renvoyée lors du dernier appel de ce même lambda.
- Le second est du même type que chaque élément de la collection.
Comme pour les autres fonctions que vous avez vues, l'expression lambda est appelée pour chaque élément d'une collection. Vous pouvez donc utiliser fold()
pour additionner tous les éléments de façon concise.
Utilisons fold()
pour calculer le prix total de tous les cookies.
- Dans
main()
, créez une variable appeléetotalPrice
et définissez-la sur le résultat de l'appel defold()
sur la listecookies
. Transmettez0.0
comme valeur initiale. Son type est donc fixé surDouble
.
val totalPrice = cookies.fold(0.0) {
}
- Vous devez spécifier les deux paramètres pour l'expression lambda. Utilisez
total
pour l'accumulateur etcookie
pour l'élément de collection. Utilisez une flèche (->
) après la liste des paramètres.
val totalPrice = cookies.fold(0.0) {total, cookie ->
}
- Dans le corps du lambda, calculez la somme de
total
et decookie.price
. Cette somme devient la valeur renvoyée et est transmise àtotal
la prochaine fois que le lambda est appelé.
val totalPrice = cookies.fold(0.0) {total, cookie ->
total + cookie.price
}
- Affichez la valeur de
totalPrice
, sous la forme d'une chaîne pour une meilleure lisibilité.
println("Total price: $${totalPrice}")
- Exécutez votre code. Le résultat doit être égal à la somme des prix de la liste
cookies
.
... Total price: $10.83
7. sortedBy()
Lorsque vous avez découvert les collections, vous avez appris que la fonction sort()
pouvait être utilisée pour trier les éléments. Cependant, cette fonction ne fonctionnera pas sur une collection d'objets Cookie
. La classe Cookie
comporte plusieurs propriétés, et Kotlin ne peut pas savoir sur lesquelles effectuer le tri (name
, price
, etc.).
Pour ce type de situations, les collections Kotlin proposent la fonction sortedBy()
. sortedBy()
vous permet de spécifier un lambda qui renvoie la propriété sur laquelle vous souhaitez effectuer le tri. Par exemple, si vous souhaitez effectuer un tri en fonction de la propriété price
, le lambda renvoie it.price
. Tant que le type de données de la valeur possède un ordre de tri naturel (les chaînes sont triées par ordre alphabétique et les valeurs numériques triées par ordre croissant), les données sont triées de la même façon qu'une collection de ce type.
Essayons d'utiliser sortedBy()
pour trier la liste des cookies par ordre alphabétique.
- Dans
main()
, après le code existant, ajoutez une variable appeléealphabeticalMenu
et attribuez-lui la valeur renvoyée par l'appel de la fonctionsortedBy()
sur la listecookies
.
val alphabeticalMenu = cookies.sortedBy {
}
- Dans l'expression lambda, renvoyez
it.name
. La liste obtenue sera de typeList<Cookie>
, mais triée en fonction dename
.
val alphabeticalMenu = cookies.sortedBy {
it.name
}
- Affichez le nom des cookies dans
alphabeticalMenu
. Vous pouvez utiliserforEach()
pour afficher chaque nom sur une nouvelle ligne.
println("Alphabetical menu:")
alphabeticalMenu.forEach {
println(it.name)
}
- Exécutez votre code. Les noms des cookies sont affichés par ordre alphabétique.
... Alphabetical menu: Banana Walnut Blueberry Tart Chocolate Chip Chocolate Peanut Butter Snickerdoodle Sugar and Sprinkles Vanilla Creme
8. Conclusion
Félicitations ! Vous venez de voir plusieurs exemples d'utilisation de fonctions d'ordre supérieur sur des collections. Les opérations les plus courantes, comme le tri et le filtrage, peuvent être effectuées avec une seule ligne de code, ce qui rend vos programmes plus clairs et concis.
Résumé
- Vous pouvez faire une boucle qui s'applique successivement à chaque élément d'une collection à l'aide de
forEach()
. - Les expressions peuvent être insérées dans des chaînes.
map()
permet de mettre en forme les éléments d'une collection, souvent en les organisant dans une collection utilisant un autre type de données.filter()
peut générer un sous-ensemble d'une collection.groupBy()
divise une collection en fonction de la valeur renvoyée par une fonction.fold()
transforme une collection en une valeur unique.sortedBy()
permet de trier une collection selon une propriété spécifiée.