Introduction
Au poste des scores de Field Day, les tentatives arrivent dans le désordre. Les trois tentatives d’Alex valent 80, 90 et 85 points. Le meilleur résultat est 90 : ce sont des résultats absolus, pas des points à additionner. Un deuxième joueur s’appelle aussi Alex ; les noms ne peuvent donc pas identifier les concurrents. Lorsque le score de la troisième place est partagé, tous les joueurs ayant ce score sont qualifiés.
Vous enregistrerez des fonctions Redis réutilisables d’enregistrement et de qualification, traiterez la véritable feuille de tentatives et publierez les véritables joueurs qualifiés. Vous réaliserez ensuite une petite épreuve chronométrée dans laquelle les scores les plus bas sont les meilleurs. Les deux exercices utilisent des identités fictives préparées et des scores entiers. Savoir naviguer dans un terminal et utiliser les connexions Redis/GET/SET sont des prérequis ; les ensembles triés et les modèles de fonctions Python fournis sont expliqués avant utilisation. Redis et Terminal sont prêts.
Travaillez dans /home/labex/project/score-desk. Créez main.py et practice.py ; mettez à jour uniquement score:main:best, score:practice:best et les clés d’essai commençant par score:demo:. Conservez les feuilles CSV d’origine, les hachages de participants, la manche précédente et tous les outils fournis. Le résultat principal de 88 points d’un joueur déjà enregistré et le résultat d’entraînement de 72 secondes d’un coureur déjà enregistré doivent être conservés, même si leurs tentatives sont absentes des nouvelles feuilles.
Une tentative doit mettre à jour le meilleur résultat de son joueur de façon sûre, même lorsqu’une autre soumission se produit en même temps. La qualification s’exécute après la clôture des soumissions ; ses deux lectures ne constituent pas un instantané d’une compétition en cours de modification. Les scores sont des entiers de 1 à 1000000. Cet exercice ne met pas en œuvre la contestation des scores, la suppression d’un meilleur résultat erroné, la détection de triche, le chronométrage fractionnaire ni la persistance/le basculement.
Conserver la meilleure tentative absolue
Dans cette étape, vous conserverez la tentative absolue la plus élevée de chaque joueur sans laisser un résultat inférieur arrivé tardivement la remplacer.
Utilisez Terminal, en commençant dans le shell ordinaire. Le programme desk fourni lit la véritable feuille et la base. Il appelle vos fonctions enregistrées sans les modifier ; il ne calcule ni ne corrige le meilleur résultat pour vous. ./ signifie exécuter un fichier du répertoire courant. Changez de répertoire et examinez la feuille ainsi que la liste des participants :
cd /home/labex/project/score-desk
./desk attempts main
./redis HGETALL score:main:names
./desk data main
player_id est une identité stable ; score est le total absolu de points d’une tentative. Les identifiants p01 et p02 appartiennent tous deux à des personnes nommées Alex. L’ensemble trié principal préparé ne contient actuellement que p90 avec le score 88. Il doit rester présent. Redis stocke les noms séparément dans un hachage, une correspondance entre identifiant de joueur et nom. HGETALL lit cette correspondance ; vous n’avez pas besoin de la modifier.
Un ensemble trié stocke chaque membre une seule fois avec un score numérique. Ici, le membre est un identifiant de joueur. Redis ordonne les membres par score. La commande ordinaire ZADD key score member ajoute un nouveau membre ou remplace son score existant. Utilisez une clé de démonstration jetable pour voir pourquoi la dernière arrivée n’est pas nécessairement la meilleure :
./redis ZADD score:demo:last 80 p01
./redis ZADD score:demo:last 90 p01
./redis ZADD score:demo:last 85 p01
./redis --raw ZSCORE score:demo:last p01
Le score final est 85, car la dernière commande a écrasé 90. ZSCORE lit le score d’un membre ; --raw demande à la CLI une valeur simple. ZINCRBY additionnerait plutôt les tentatives, ce qui donnerait 255 à partir de ces trois valeurs. Cette opération convient aux incréments, mais ces tentatives ne sont pas des incréments.
ZADD avec GT ajoute un nouveau membre ou ne modifie un score existant que si la nouvelle valeur est supérieure. La comparaison et la mise à jour se produisent dans une seule commande Redis. Une lecture distincte côté client suivie d’une écriture inconditionnelle peut entrer en concurrence avec un autre appelant. Essayez la condition atomique :
./redis ZADD score:demo:best GT 80 p01
./redis ZADD score:demo:best GT 90 p01
./redis ZADD score:demo:best GT 85 p01
./redis ZADD score:demo:best GT 90 p01
./redis --raw ZSCORE score:demo:best p01
Le score reste 90, y compris après la tentative en double. Par défaut, la réponse de ZADD compte les nouveaux membres ; 0 peut donc signifier soit une mise à jour réussie d’un membre existant, soit une tentative ignorée. Ce n’est pas une erreur. Lisez le véritable score pour distinguer ces cas. GT n’empêche pas l’insertion d’un nouveau joueur ; ajouter XX empêcherait les premières tentatives et serait incorrect ici.
Enregistrez une fonction réutilisable afin que chaque ligne soumise suive la même règle. Le client fourni qui utilise la fonction transmet un client Redis, la véritable clé, l’identifiant du joueur et le score. En Python, def définit la fonction ; les noms entre parenthèses reçoivent ces valeurs. Le return indenté envoie une commande et renvoie sa véritable réponse. Les mots entre guillemets sont des arguments littéraux de commande Redis ; les noms sans guillemets utilisent les valeurs de l’appelant. Aucun paquet Redis Python n’est à installer : le client fourni envoie des commandes Redis natives.
Le here-document suivant écrit le fichier complet. cat > main.py remplace ce fichier. Le marqueur PY entre guillemets empêche le shell d’interpréter le corps ; le PY final doit être seul sur sa ligne. Conservez les quatre espaces avant return. Collez tout le bloc dans Terminal :
cat > main.py <<'PY'
def record(client, key, player, score):
return client.command("ZADD", key, "GT", score, player)
PY
Rejouez la feuille préparée via votre fonction. Puis rejouez-la à nouveau et soumettez encore une fois la tentative inférieure arrivée tardivement :
./desk replay main
./desk data main
./desk replay main
./desk submit main p01 85
./redis --raw ZSCORE score:main:best p01
./redis ZREVRANK score:main:best p01
p01 reste à 90 ; l’autre Alex, p02, reste un membre distinct à 90. L’ensemble complet contient aussi p05=100, p03=95, p06=90, p04=70 et le joueur antérieur p90=88. data affiche en alternance les identifiants et les chaînes de score dans l’ordre croissant Redis.
ZREVRANK donne la position à partir de zéro d’un membre dans l’ordre décroissant : la première position est 0. Ici, p01 a la position 4, car les scores égaux utilisent l’ordre inverse des identifiants comparés octet par octet. Cette position ne disqualifie pas un joueur à égalité au score de qualification. Nous utiliserons les scores, plutôt qu’un nombre fixe d’identifiants, pour la qualification.
Si vous faites une faute dans un nom de fichier ou une commande, lisez l’erreur, utilisez ls pour vérifier les véritables fichiers et réessayez la commande corrigée. Une erreur Score desk error est différente d’un résultat vide réussi. Si vous avez appliqué par erreur une mauvaise règle de score, corrigez d’abord main.py, examinez la feuille d’origine et utilisez la commande ordinaire ZADD pour restaurer le véritable meilleur résultat de chaque joueur affecté ; par exemple ./redis ZADD score:main:best 90 p01. Ne modifiez pas la feuille ni la liste des participants, n’effacez pas la manche et n’écartez pas le joueur antérieur. Lancez la vérification de l’étape après avoir examiné les véritables résultats.
Inclure tous les joueurs à la limite de qualification
Dans cette étape, vous transformerez une limite de position en seuil de score inclusif afin que tous les joueurs ex æquo soient qualifiés.
Restez dans Terminal et dans le répertoire score-desk. Les soumissions sont closes pour cette partie. La règle est les trois premières positions, plus tous les joueurs à égalité au score de la troisième position. Il y a deux décisions distinctes : trouver ce score, puis inclure tous les joueurs qui satisfont ce seuil.
ZRANGE lit un ensemble trié. Sans option de score, ses bornes sont des positions inclusives à partir de zéro. REV inverse l’ordre des scores ; WITHSCORES inclut le score de chaque membre. La troisième position a l’indice 2 :
./redis --raw ZRANGE score:main:best 0 2 REV WITHSCORES
./redis --raw ZRANGE score:main:best 2 2 REV WITHSCORES
La première commande renvoie uniquement p05, p03 et p06. La seconde renvoie p06 et 90 : le score limite est 90. S’arrêter à trois joueurs ferait perdre p02 et p01, qui sont tous deux qualifiés selon la règle d’égalité annoncée.
Avec BYSCORE, les bornes sont des scores, pas des positions. Dans l’ordre inverse, donnez d’abord la borne supérieure. +inf signifie aucune limite supérieure. Une borne de score simple inclut l’égalité ; une borne commençant par ( l’exclurait et ferait perdre à tort tous les ex æquo au seuil. Lisez l’ensemble complet des qualifiés :
./redis --raw ZRANGE score:main:best +inf 90 BYSCORE REV WITHSCORES
Les cinq identifiants apparaissent, dans l’ordre p05, p03, p06, p02, p01. L’ordre des identifiants à score égal est une convention d’affichage fournie par Redis, pas une règle permettant d’éliminer des ex æquo ou de trier leurs noms.
Faites fonctionner cela pour tout k positif. Le client renvoie une liste Python de chaînes alternées : pour un seul joueur au seuil, elle vaut ["p06", "90"]. L’indice 0 de la liste est l’identifiant et l’indice 1 est le score. boundary[1] utilise donc le véritable score de Redis. k - 1 convertit la position demandée en indice à partir de zéro. L’affectation = stocke cette réponse dans la variable locale boundary.
if not boundary teste si la liste est vide. Lorsque k dépasse le nombre de joueurs, tout le monde est qualifié ; lisez les positions de 0 à -1, où -1 désigne le dernier membre. Pour une manche vide, cela renvoie aussi une liste réellement vide. Ce repli utilise le véritable ensemble Redis, pas une réponse d’exemple.
Ajoutez la fonction suivante à main.py. Ici, >> ajoute au lieu de remplacer la fonction d’enregistrement existante. Python utilise l’indentation supplémentaire sous if pour identifier son retour conditionnel :
cat >> main.py <<'PY'
def qualify(client, key, k):
boundary = client.command("ZRANGE", key, k - 1, k - 1, "REV", "WITHSCORES")
if not boundary:
return client.command("ZRANGE", key, 0, -1, "REV", "WITHSCORES")
return client.command("ZRANGE", key, "+inf", boundary[1], "BYSCORE", "REV", "WITHSCORES")
PY
L’action board fournie appelle qualify, puis associe les noms de la véritable liste des participants. Elle préserve les membres, valeurs et ordre que vous renvoyez. Elle ne refiltre pas, n’ajoute pas un ex æquo manquant et ne remplace pas votre seuil par un seuil correct. Essayez différentes demandes :
./desk board main 3
./desk board main 1
./desk board main 100
La première demande renvoie cinq objets joueur. Les deux enregistrements Alex apparaissent avec des valeurs player_id différentes. La seconde renvoie seulement p05 à 100. La dernière renvoie les sept joueurs, y compris p90 à 88 et p04 à 70. Lire un classement ne soumet aucune nouvelle tentative.
Les deux lectures de cette fonction sont prévues pour une manche close. Elles ne garantissent pas un instantané cohérent unique pendant que les soumissions continuent. La commande conditionnelle unique d’enregistrement est atomique ; ce sont deux affirmations différentes. Conservez votre fonction d’enregistrement opérationnelle et lancez la vérification de l’étape.
Appliquer les mêmes règles à une épreuve chronométrée
Dans cette étape, vous construirez et utiliserez une méthode complète d’enregistrement et de qualification où le plus bas est le meilleur, sur une manche d’entraînement indépendante.
Restez dans Terminal et dans le même répertoire. Une courte course d’entraînement enregistre des secondes écoulées entières positives ; les scores les plus bas sont donc les meilleurs. Sa feuille indépendante comporte de nouveaux identifiants ; le coureur antérieur t90 possède déjà 72 secondes. Examinez-la avant d’écrire :
./desk attempts practice
./redis HGETALL score:practice:names
./desk data practice
t01 a des tentatives de 70, 60 et 65 secondes. Son meilleur résultat est 60, pas 65. LT ne modifie un score existant que si la nouvelle valeur est inférieure ; il ajoute toujours les nouveaux membres. Comparez les trois mêmes tentatives sur une clé d’essai :
./redis ZADD score:demo:time LT 70 t01
./redis ZADD score:demo:time LT 60 t01
./redis ZADD score:demo:time LT 65 t01
./redis --raw ZSCORE score:demo:time t01
Le résultat reste 60. Le raisonnement sur la mise à jour atomique et la sûreté des répétitions est le même que pour GT ; seul le sens d’amélioration change.
Pour un classement où le plus bas est le meilleur, utilisez l’ordre croissant en omettant REV. À la position limite k - 1, incluez tous les joueurs dont le score est inférieur ou égal à ce score. Une plage de scores dans le sens croissant commence par -inf, qui signifie aucune limite inférieure. Enregistrez le modèle indépendant complet dans practice.py ; il utilise exactement les mêmes interfaces de fonction que la manche principale :
cat > practice.py <<'PY'
def record(client, key, player, score):
return client.command("ZADD", key, "LT", score, player)
def qualify(client, key, k):
boundary = client.command("ZRANGE", key, k - 1, k - 1, "WITHSCORES")
if not boundary:
return client.command("ZRANGE", key, 0, -1, "WITHSCORES")
return client.command("ZRANGE", key, "-inf", boundary[1], "BYSCORE", "WITHSCORES")
PY
Soumettez la feuille d’entraînement, examinez la borne native et la plage inclusive, puis demandez son résultat au véritable client de classement :
./desk replay practice
./redis --raw ZRANGE score:practice:best 1 1 WITHSCORES
./redis --raw ZRANGE score:practice:best -inf 60 BYSCORE WITHSCORES
./desk board practice 2
./desk replay practice
./desk submit practice t01 65
./desk board practice 2
./desk board practice 100
./desk board main 3
La borne est t01 à 60. Le classement des deux premiers avec ex æquo contient t03=50, t01=60, t02=60 et t04=60. Les noms Sam identiques appartiennent à des identifiants différents. Le 65 tardif ne peut pas remplacer 60, et rejouer toute la feuille ne change aucun meilleur résultat. La grande limite inclut les six coureurs, dont t90=72. La qualification principale à cinq joueurs reste disponible.
Pour les temps égaux, Redis trie les identifiants par ordre croissant octet par octet ; le classement principal aux scores les plus élevés utilise l’ordre décroissant octet par octet à cause de REV. Aucune de ces conventions n’exclut un participant à score égal. Pour ces identifiants ASCII préparés, l’ordre indiqué est stable.
Vous disposez maintenant de méthodes complètes d’enregistrement et de qualification pour les deux sens. Conservez les deux fichiers, les deux manches, les feuilles d’origine, les listes des participants et les résultats de la manche antérieure. Les vérifications exécutent vos fonctions inchangées sur d’autres identifiants, valeurs, ordres d’arrivée et limites. Lancez la vérification finale après avoir examiné les véritables résultats.
Résumé
Vous avez conservé des identités de joueurs stables, enregistré les meilleures tentatives absolues avec des mises à jour Redis conditionnelles atomiques et distingué le sens d’amélioration des scores de l’ordre d’affichage. Vous avez utilisé le véritable score à la position k pour inclure tous les ex æquo à la limite de qualification, y compris les joueurs homonymes, et préservé les résultats antérieurs. Vos fonctions enregistrées fonctionnent aussi bien pour les épreuves au score le plus élevé que pour les épreuves chronométrées en secondes entières. Les lectures de qualification supposent la clôture des soumissions ; la persistance, les contestations et les instantanés en direct sont des travaux distincts.



