Introducción
En el puesto de puntuaciones de Field Day, los intentos llegan fuera de orden. Los tres intentos de Alex obtienen 80, 90 y 85 puntos. El mejor es 90: son resultados absolutos, no puntos que sumar. Un segundo jugador también se llama Alex, por lo que los nombres no pueden identificar a los competidores. Cuando hay empate en la puntuación del tercer puesto, se clasifican todos los que tienen esa puntuación.
Guardarás funciones reutilizables de registro y clasificación en Redis, procesarás la hoja real de intentos y publicarás los jugadores realmente clasificados. Después completarás una pequeña prueba cronometrada en la que las puntuaciones menores son mejores. Ambos ejercicios usan identidades sintéticas preparadas y puntuaciones enteras. Se requieren conocimientos básicos para moverse por la terminal y sobre conexiones de Redis y GET/SET; los conjuntos ordenados y las plantillas de funciones Python proporcionadas se explican antes de usarlos. Redis y Terminal están preparados.
Trabaja en /home/labex/project/score-desk. Crea main.py y practice.py; actualiza únicamente score:main:best, score:practice:best y las claves de prueba que empiecen por score:demo:. Conserva las hojas CSV originales, los hashes de participantes, la ronda anterior y todas las herramientas proporcionadas. El resultado principal previo de 88 puntos de un jugador y el resultado previo de 72 segundos de un corredor en la práctica deben conservarse, aunque sus intentos no estén en las hojas nuevas.
Un único intento debe actualizar la mejor marca de su jugador de forma segura incluso si coincide con otro envío. La clasificación se ejecuta después de cerrar los envíos; sus dos lecturas no son una instantánea de una competición que está cambiando. Las puntuaciones son enteros de 1 a 1000000. Este ejercicio no implementa reclamaciones de puntuación, eliminación de una mejor marca errónea, detección de trampas, tiempos fraccionarios ni persistencia o conmutación por error.
Conservar el mejor intento absoluto
En este paso, conservarás el intento absoluto más alto de cada jugador sin permitir que un resultado inferior tardío lo reemplace.
Usa Terminal, empezando en el intérprete normal del sistema. El programa proporcionado desk lee la hoja y la base de datos reales. Llama a tus funciones guardadas sin modificarlas; no calcula ni corrige por ti la mejor marca. ./ significa ejecutar un archivo del directorio actual. Cambia de directorio y consulta la hoja y la lista de participantes:
cd /home/labex/project/score-desk
./desk attempts main
./redis HGETALL score:main:names
./desk data main
player_id es una identidad estable; score es la puntuación absoluta de un intento. Los ID p01 y p02 pertenecen a personas llamadas Alex. El conjunto ordenado principal preparado contiene actualmente solo p90 con una puntuación de 88. Debe seguir presente. Redis almacena los nombres por separado en un hash, una correspondencia entre el ID de jugador y su nombre. HGETALL lee esa correspondencia; no necesitas editarla.
Un conjunto ordenado almacena cada miembro una vez con una puntuación numérica. Aquí el miembro es un ID de jugador. Redis ordena los miembros por puntuación. ZADD key score member sin opciones añade un miembro nuevo o reemplaza su puntuación existente. Usa una clave de demostración desechable para ver por qué el último resultado que llega no es necesariamente el mejor:
./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
La puntuación final es 85, porque el último comando sobrescribió 90. ZSCORE lee la puntuación de un miembro; --raw solicita a la interfaz de línea de comandos un valor sin formato adicional. ZINCRBY sumaría los intentos y produciría 255 a partir de estos tres valores. Esa operación es adecuada para incrementos, pero estos intentos no son incrementos.
ZADD con GT añade un miembro nuevo o cambia una puntuación existente solo cuando el nuevo valor es mayor. La comparación y la actualización ocurren dentro de un único comando de Redis. Una lectura separada en el cliente seguida de una escritura incondicional puede entrar en una condición de carrera con otra llamada. Prueba la condición atómica:
./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
La puntuación sigue siendo 90, incluso después del intento duplicado. De forma predeterminada, la respuesta de ZADD cuenta miembros nuevos, por lo que 0 puede significar tanto una actualización correcta de un miembro existente como un intento ignorado. No es un error. Lee la puntuación real para distinguir estos casos. GT no impide insertar un jugador nuevo; añadir XX impediría los primeros intentos y aquí sería incorrecto.
Guarda una función reutilizable para que todas las filas enviadas sigan la misma regla. El consumidor proporcionado pasa un cliente Redis, la clave real, el ID de jugador y la puntuación. def de Python define la función; los nombres entre paréntesis reciben esos valores. El return con sangría envía un comando y devuelve su respuesta real. Las palabras entre comillas son argumentos literales del comando Redis; los nombres sin comillas usan los valores de quien llama. No hay ningún paquete Redis de Python que instalar: el cliente proporcionado envía comandos nativos de Redis.
El siguiente here-document escribe el archivo completo. cat > main.py reemplaza ese archivo. El marcador PY entre comillas impide que el intérprete interprete el cuerpo; el PY de cierre debe estar solo en su línea. Conserva los cuatro espacios anteriores a return. Pega el bloque entero en Terminal:
cat > main.py <<'PY'
def record(client, key, player, score):
return client.command("ZADD", key, "GT", score, player)
PY
Reproduce los envíos de la hoja preparada mediante tu función. Después vuelve a reproducirlos y envía una vez más el intento inferior tardío:
./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 sigue en 90; el otro Alex, p02, permanece como miembro independiente con 90. El conjunto completo también contiene p05=100, p03=95, p06=90, p04=70 y el resultado previo p90=88. data muestra ID y cadenas de puntuación alternados en el orden ascendente de Redis.
ZREVRANK indica la posición basada en cero de un miembro en orden descendente: la primera posición es 0. Aquí p01 tiene la posición 4, porque las puntuaciones iguales usan el orden inverso de los ID comparados byte a byte. Esa posición no descalifica a un jugador empatado en la puntuación de corte. Usaremos puntuaciones, en lugar de una cantidad fija de ID, para la clasificación.
Si escribes mal un nombre de archivo o un comando, lee el error, usa ls para comprobar los archivos reales y vuelve a intentar el comando corregido. Un Score desk error es distinto de un resultado vacío correcto. Si aplicaste por accidente una regla de puntuación incorrecta, corrige primero main.py, consulta la hoja original y usa ZADD sin opciones para restaurar la verdadera mejor marca de cada jugador afectado; por ejemplo, ./redis ZADD score:main:best 90 p01. No edites la hoja ni la lista de participantes, borres la ronda ni descartes al jugador previo. Ejecuta la comprobación del paso después de inspeccionar los resultados reales.
Incluir a todos los empatados en el corte
En este paso, convertirás un corte por posición en un límite inclusivo de puntuación para que se clasifiquen todos los jugadores empatados.
Permanece en Terminal y en el directorio score-desk. Los envíos están cerrados para esta parte. La regla es las tres primeras posiciones más todos los empatados en la puntuación de la tercera posición. Hay dos decisiones distintas: encontrar esa puntuación e incluir después a todos los jugadores que la alcancen.
ZRANGE lee un conjunto ordenado. Sin una opción de puntuación, sus límites son posiciones inclusivas basadas en cero. REV invierte el orden de puntuación; WITHSCORES incluye la puntuación de cada miembro. La tercera posición tiene el índice 2:
./redis --raw ZRANGE score:main:best 0 2 REV WITHSCORES
./redis --raw ZRANGE score:main:best 2 2 REV WITHSCORES
El primer comando devuelve solo p05, p03 y p06. El segundo devuelve p06 y 90: la puntuación de corte es 90. Detenerse en tres jugadores dejaría fuera a p02 y p01, que se clasifican según la regla de empates indicada.
Con BYSCORE, los límites son puntuaciones, no posiciones. En orden inverso, proporciona primero el límite superior. +inf significa que no hay límite superior. Un límite de puntuación sin prefijo incluye la igualdad; un límite que empiece por ( excluiría la igualdad y perdería incorrectamente todos los empates del corte. Consulta el conjunto completo de clasificados:
./redis --raw ZRANGE score:main:best +inf 90 BYSCORE REV WITHSCORES
Aparecen los cinco ID, ordenados como p05, p03, p06, p02, p01. El orden de los ID con igual puntuación es una convención de visualización de Redis, no una regla para eliminar jugadores empatados ni para ordenar sus nombres.
Haz que funcione para cualquier k positivo. El cliente devuelve una lista de Python de cadenas alternadas: para un único jugador en el límite es ["p06", "90"]. El índice 0 de la lista es el ID y el índice 1 es la puntuación. Por tanto, boundary[1] usa la puntuación real de Redis. k - 1 convierte la posición solicitada en un índice basado en cero. La asignación = almacena esa respuesta en la variable local boundary.
if not boundary comprueba si la lista está vacía. Cuando k es mayor que el número de jugadores, todos se clasifican; lee las posiciones de 0 a -1, donde -1 significa el último miembro. Para una ronda vacía, esto también devuelve una lista vacía real. Esta alternativa usa el conjunto real de Redis, no una respuesta de ejemplo.
Añade la siguiente función al final de main.py. Aquí >> añade en lugar de reemplazar la función de registro existente. Python usa la sangría adicional bajo if para identificar su retorno condicional:
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
La acción proporcionada board invoca qualify y después adjunta nombres de la lista real de participantes. Conserva los miembros, los valores y el orden que devuelves. No vuelve a filtrar ni añade un empate que falte ni sustituye el corte por uno correcto. Prueba solicitudes diferentes:
./desk board main 3
./desk board main 1
./desk board main 100
La primera solicitud devuelve cinco objetos de jugador. Aparecen ambos registros de Alex con valores player_id distintos. La segunda devuelve solo p05 con 100. La última devuelve los siete jugadores, incluidos p90 con 88 y p04 con 70. Leer una tabla de clasificación no envía intentos nuevos.
Las dos lecturas de esta función están pensadas para una ronda cerrada. No garantizan una única instantánea coherente mientras continúan los envíos. El comando único de registro condicional es atómico; son afirmaciones diferentes. Mantén operativa tu función de registro y ejecuta la comprobación del paso.
Aplicar las mismas reglas a una prueba cronometrada
En este paso, crearás y usarás un método completo de registro y clasificación en el que una puntuación menor es mejor, sobre una ronda de práctica independiente.
Permanece en Terminal y en el mismo directorio. Una carrera corta de práctica registra segundos transcurridos enteros positivos, por lo que las puntuaciones menores son mejores. Su hoja independiente tiene ID nuevos; el corredor previo t90 ya tiene 72 segundos. Consulta los datos antes de escribir:
./desk attempts practice
./redis HGETALL score:practice:names
./desk data practice
t01 tiene intentos de 70, 60 y 65 segundos. Su mejor marca es 60, no 65. LT cambia una puntuación existente solo cuando el nuevo valor es menor; sigue añadiendo miembros nuevos. Compara los mismos tres intentos en una clave de prueba:
./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
El resultado sigue siendo 60. El razonamiento sobre la actualización atómica y la seguridad ante duplicados es el mismo que para GT; solo cambia la dirección de mejora.
Para una clasificación en la que menor es mejor, usa el orden ascendente omitiendo REV. En la posición de corte k - 1, incluye a todos los que tengan una puntuación menor o igual que esa puntuación. Un rango de puntuaciones en orden ascendente empieza por -inf, que significa sin límite inferior. Guarda la plantilla independiente completa en practice.py; usa exactamente las mismas interfaces de función que la ronda principal:
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
Envía la hoja de práctica, consulta el límite nativo y el rango inclusivo y después pide su resultado al consumidor real de la tabla de clasificación:
./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
El límite es t01 con 60. La clasificación de las dos primeras posiciones con empates contiene t03=50, t01=60, t02=60 y t04=60. Los nombres repetidos Sam pertenecen a ID distintos. El 65 tardío no puede reemplazar al 60 y reproducir toda la hoja no cambia ninguna mejor marca. El corte grande incluye a los seis corredores, incluido t90=72. La clasificación principal de cinco jugadores sigue disponible.
Para tiempos iguales, Redis ordena los ID de forma ascendente byte a byte; la tabla principal de puntuaciones altas usa orden descendente byte a byte debido a REV. Ninguna de las dos convenciones excluye a un participante con igual puntuación. Para estos ID ASCII preparados, el orden indicado es estable.
Ahora tienes métodos completos de registro y clasificación para ambas direcciones. Conserva ambos archivos, ambas rondas, las hojas originales, las listas de participantes y los resultados de rondas previas. Las comprobaciones ejercitan tus funciones sin cambios con otros ID, valores, órdenes de llegada y cortes. Ejecuta la comprobación final después de inspeccionar los resultados reales.
Resumen
Conservaste identidades estables de jugadores, almacenaste mejores intentos absolutos mediante actualizaciones condicionales atómicas de Redis y separaste la dirección de mejora de la puntuación del orden de visualización. Usaste la puntuación real de la posición k para incluir todos los empates en el corte, incluidos jugadores con el mismo nombre, y conservaste resultados previos. Tus funciones guardadas funcionan tanto para pruebas donde gana la puntuación más alta como para pruebas cronometradas en segundos enteros. Las lecturas de clasificación suponen que los envíos ya se han cerrado; la persistencia, las reclamaciones y las instantáneas en vivo son trabajos independientes.



