Explica los candidatos más próximos

PythonBeginner
Practicar Ahora

Introducción

La biblioteca ya identifica los juegos utilizables esta noche. Ahora necesita alternativas similares transparentes, con explicaciones suficientes para elegir. Calcularás distancias a partir de dos características públicas, mostrarás diferencias con signo en sus unidades originales y conservarás todos los empates del límite de selección.

Se suponen los conceptos de idoneidad con Python y escala MinMax del laboratorio anterior. Este entorno es independiente: incluye todas las entradas y el auxiliar de idoneidad completo, sin necesitar archivos ni procesos previos. Implementarás la clasificación y producirás JSON reales que el lector muestra sin completar resultados. Los 50 minutos son una estimación del autor. Esta similitud no predice gustos ni demuestra rendimiento en catálogos grandes.

Mide cada alternativa adecuada

En este paso construirás una tabla completa de distancias y mostrarás qué cambia cada candidato respecto a la referencia.

Abre la pestaña Terminal y entra en el espacio independiente. Abrir o reconectar esta entrada no reinstala el espacio de trabajo ni borra el progreso. cd establece el directorio de este terminal; repítelo en uno nuevo.

cd /home/labex/project/board-games

El catálogo usa claves únicas game_id, títulos solo para mostrar, duración en minutes, valoración complexity de 1 a 5 y límites inclusivos min_players/max_players. El CSV de disponibilidad contiene un true/false por ID en otro orden. night.json identifica la referencia, los players y el k deseado. La propia referencia puede no estar disponible o ser inadecuada: solo los candidatos deben cumplir las condiciones de esta noche. Son legales los títulos repetidos con ID diferentes y las características idénticas con ID diferentes, incluso a distancia cero. Se excluye únicamente el reference_id exacto.

cat muestra texto sin modificarlo. Revisa la solicitud y el auxiliar suministrado, que implementa el trabajo anterior.

cat night.json
cat suitability.py

Ejecuta esa interfaz existente antes de añadir un cálculo de distancia. --mode suitable invoca choose_games; --output, obligatorio, nombra el JSON que se escribirá. Sin otras opciones de entrada, el ejecutor lee catalog.csv, availability.csv y night.json del directorio actual. El lector imprime solo el archivo que guardaste.

python3 planner.py --mode suitable --output suitable.json
python3 show_results.py suitable.json

Debes ver los seis ID elegibles alfabéticos left, near, raw, right, same y zero, con la escala indicada debajo. Es un resultado de idoneidad que ya funciona, no una recomendación completa: el módulo de clasificación aún tiene regiones de implementación vacías.

choose_games relaciona disponibilidad por ID y devuelve todos los ID elegibles junto con una escala obtenida de todo el catálogo, antes de filtrar. El orden es minutes, luego complexity. scale.min vale [10, 1] y scale.range, [100, 4]. Reutiliza los mismos parámetros para consulta y candidatos: prestar un juego no debe mover la regla. No hay etiquetas objetivo ni división supervisada entrenamiento/prueba.

planner.py gestiona opciones y formatos mediante coursekit.py, pero no elige ni ordena juegos. Puedes modificar suitability.py, ranking.py y resultados; conserva todos los CSV/JSON originales, planner.py, coursekit.py, show_results.py y ambos starter-*.py.

Para un valor x, MinMax calcula (x - minimum) / range. Una característica constante conserva rango cero pero usa divisor uno; como la consulta pertenece al catálogo, todas sus diferencias son cero. Para consulta escalada q y candidato c, la distancia euclídea es sqrt((c_minutes-q_minutes)^2 + (c_complexity-q_complexity)^2). Ambas características pesan igual después de escalar. Una distancia menor significa mayor similitud bajo esa regla, no mayor disfrute.

La explicación original se calcula aparte: minutes_delta y complexity_delta son candidato menos referencia, antes de escalar. Un delta de minutos negativo significa un juego más corto. Deben proceder del candidato real identificado por ese ID, no de otra fila ni de su distancia.

Examina la estructura inicial de clasificación antes de editarla. from suitability import choose_games importa el auxiliar local. Las líneas suministradas prepared, by_id y reference conectan ese resultado y la consulta con los registros del catálogo asociados por ID. La lista rows vacía, el bucle de candidatos, la ordenación y el retorno final aportan la estructura circundante. Tres comentarios marcan las regiones que completarás; no calculan las distancias ni la selección que faltan.

cat ranking.py

Haz una copia del código fuente. cp copia el archivo actual; esta copia es la base incompleta, no una respuesta terminada.

cp ranking.py ranking.py.bak

np.where usa uno para rangos cero y el rango real en los demás casos. np.linalg.norm obtiene la longitud euclídea del vector de dos diferencias. float convierte valores NumPy en números compatibles con JSON. La clave de ordenación es una pareja: distancia sin redondear y, si es exactamente igual, game_id. La breve lambda define esa clave por fila. No redondees antes de ordenar. Diferencias mínimas de coma flotante entre fórmulas equivalentes pueden alterar el orden de valores casi iguales; los decimales mostrados no definen el orden almacenado.

Abre el módulo en nano. Como en el primer laboratorio, LC_ALL=C.UTF-8 se aplica solo a esta invocación del editor. Mantén cuatro espacios por nivel de sangría; Python usa la sangría para agrupar instrucciones.

LC_ALL=C.UTF-8 nano ranking.py

Pulsa Ctrl+W, busca ## Metric parameters y pulsa Enter. Baja al pass siguiente, pulsa Home y después Ctrl+K una vez para quitar esa línea provisional. Inserta estas cinco líneas con sus cuatro espacios iniciales. Conserva rows = [] y la función que las rodea.

    minimum = np.array(prepared['scale']['min'])
    ranges = np.array(prepared['scale']['range'])
    divisor = np.where(ranges == 0, 1, ranges)
    query = np.array([reference['minutes'], reference['complexity']])
    query_scaled = (query - minimum) / divisor

Pulsa después Ctrl+W, busca ## Candidate record y pulsa Enter. Baja al pass de esa región, pulsa Home y Ctrl+K una vez. Inserta el bloque siguiente. Está dentro del bucle existente, así que sus instrucciones empiezan con ocho espacios; los campos dentro de rows.append usan doce. Conserva sin cambios la línea for, game = by_id[game_id] y el rows.sort posterior.

        values = np.array([game['minutes'], game['complexity']])
        candidate_scaled = (values - minimum) / divisor
        distance = float(np.linalg.norm(candidate_scaled - query_scaled))
        difference = values - query
        rows.append({
            'game_id': game_id, 'title': game['title'], 'distance': distance,
            'minutes_delta': float(difference[0]),
            'complexity_delta': float(difference[1]),
        })

Por ahora deja la región ## Cutoff selection como cutoff = None y selected = []. Permite leer esta tabla intermedia dejando explícitamente sin terminar la selección. Pulsa Ctrl+O, Enter y después Ctrl+X para guardar y salir. Si detectas una edición no deseada antes de guardar, Ctrl+X y después N descartan esta sesión del editor; vuelve a abrir y corrige solo la región prevista.

Ejecuta planner.py en modo recommend. --output nombra el archivo real. Sin otras opciones lee catalog.csv, availability.csv y night.json del directorio actual; --catalog, --availability y --request aceptan rutas explícitas.

python3 planner.py --mode recommend --output recommendations.json

Muestra tu JSON guardado con el lector. Solo abre ese archivo: no vuelve a calcular desde el catálogo ni aporta filas ausentes.

python3 show_results.py recommendations.json

Debes ver seis filas. zero tiene distancia cero; near y same, alrededor de 0.1; left/right, 0.3. raw ronda 0.5001 aunque solo dura un minuto más, porque su complejidad aumenta dos puntos. Minutes +/- y Complexity +/- muestran diferencias con signo en unidades originales. Esta versión deja deliberadamente Cutoff: None y ningún ID seleccionado; completarás la selección después.

Si aparece SyntaxError o IndentationError, lee el archivo y número de línea indicados, vuelve a abrir ranking.py y corrige esa línea o región. No sobrescribas todo el módulo para reparar una línea. Un archivo ausente suele indicar un directorio incorrecto: pwd lo muestra y el cd inicial devuelve la ubicación correcta, como practicaste en el primer laboratorio. cp ranking.py.bak ranking.py restaura la base incompleta solo si quieres abandonar deliberadamente el trabajo de este paso. Conserva los archivos de entrada en lugar de editarlos para superar la comprobación.

Conserva el conjunto completo del umbral

En este paso completarás la selección y usarás el mismo programa en noches con pocos candidatos y sin candidatos.

Continúa en la pestaña Terminal, usando este directorio para todos los comandos.

cd /home/labex/project/board-games

Aquí k es un mínimo deseado cuando existen suficientes juegos adecuados, no una promesa de exactamente k filas. Primero calcula y ordena todas las distancias elegibles. Con N candidatos, el umbral es la distancia en la posición min(k, N) de la lista ordenada. Los índices de Python empiezan en cero, por lo que corresponde a min(k, N) - 1. Conserva todos los candidatos con distancia sin redondear como máximo cutoff + 1e-9. El margen absoluto 1e-9 define empates del umbral; no agrupa distancias para ordenarlas ni permite redondearlas previamente.

Si hay menos de k, devuelve los que existen. Sin candidatos, devuelve listas vacías candidates y selected_ids, y umbral JSON null (None en Python). No añadas un juego no disponible o inadecuado como alternativa de respaldo. Tomar primero los k vecinos de todo el catálogo y filtrar después puede omitir alternativas legales posteriores; el auxiliar que filtra antes evita ese error.

La solicitud normal pide k=2. zero es el más próximo y near/same comparten el umbral según la regla de empate: deben seleccionarse los tres. El orden sigue las filas realmente ordenadas sin redondear; game_id deshace empates exactos. Conserva las seis filas para mostrar qué se consideró.

Haz una copia de la tabla de distancias que ya funciona antes de añadir la selección. Esta copia conserva la métrica y los registros de candidatos que acabas de probar.

cp ranking.py ranking-distances.py.bak

Abre el mismo módulo, sin sustituir el trabajo anterior.

LC_ALL=C.UTF-8 nano ranking.py

Pulsa Ctrl+W, busca ## Cutoff selection y pulsa Enter. Baja a cutoff = None, pulsa Home y después Ctrl+K dos veces para quitar solo esa línea y selected = []. Inserta este bloque con cuatro espacios para if y else, y ocho dentro de cada rama. El retorno final permanece después del bloque con su sangría existente.

    if rows:
        cutoff = rows[min(request['k'], len(rows)) - 1]['distance']
        selected = [row['game_id'] for row in rows
                    if row['distance'] <= cutoff + 1e-9]
    else:
        cutoff = None
        selected = []

La condición if rows evita indexar una lista vacía. Una comprensión de lista conserva los ID coincidentes en el orden ya establecido. Mantén los parámetros de la métrica, el bucle de candidatos y la ordenación del paso 1. Guarda con Ctrl+O, Enter y sal con Ctrl+X. Si necesitas deshacer el trabajo de este paso, cp ranking-distances.py.bak ranking.py restaura la versión probada de la tabla de distancias, con la selección aún incompleta; vuelve a editar el umbral en lugar de repetir todo el archivo.

El JSON requiere scale.min y scale.range en orden de características; por candidato, game_id, title, distance numérica, minutes_delta y complexity_delta con signo; además de cutoff y selected_ids. Guarda números finitos con la precisión calculada, no cadenas redondeadas. Puedes añadir explicaciones inocuas. La evaluación comprueba valores por ID con tolerancias absoluta y relativa 1e-8, distintas del empate 1e-9. Admite fórmulas equivalentes y elecciones normales de implementación, no puntúa preferencias. Ambas interfaces deben seguir funcionando: choose_games devuelve eligible_ids alfabéticos y escala completa; rank_games, la recomendación. La clasificación puede implementarse independientemente y no tiene que llamar al auxiliar. No se exige otro archivo de idoneidad en el espacio de trabajo.

Regenera y examina el resultado normal.

python3 planner.py --mode recommend --output recommendations.json
python3 show_results.py recommendations.json

Ahora estarán seleccionados zero, near y same; las demás filas siguen presentes. cat recommendations.json permite ver el JSON y sus distancias sin redondear.

Otras dos solicitudes preparadas cambian solo la noche, no catálogo ni disponibilidad. Usa cat para examinarlas. --request elige una entrada para ese comando: no modifica night.json ni establece una variable del terminal. Una salida distinta conserva el resultado normal.

cat sparse-night.json
python3 planner.py --mode recommend --request sparse-night.json --output sparse.json
python3 show_results.py sparse.json

Ocho jugadores solo pueden usar right, aunque k=5. Un candidato honesto es lo correcto. La escala sigue siendo la del catálogo completo y la distancia continúa siendo 0.3.

cat empty-night.json
python3 planner.py --mode recommend --request empty-night.json --output empty.json
python3 show_results.py empty.json

Veinte jugadores no tienen juegos adecuados. Deben aparecer cero filas, Cutoff: None y un mensaje explícito de ausencia de juegos. En Terminal, cat empty.json muestra el umbral guardado como null. El lector muestra tu resultado vacío, no elige una opción de respaldo.

Se requieren los tres archivos: recommendations.json, sparse.json y empty.json, cada uno correspondiente a su solicitud y regenerable desde entradas intactas. Para recuperarte de una edición accidental de código, vuelve a abrir nano y corrige la región afectada, conservando el resto del trabajo completo; después vuelve a ejecutar los tres comandos. cp starter-ranking.py ranking.py restaura solo el código incompleto si deseas practicar de nuevo. Reabrir el terminal no reinicia datos ni ejecuta setup.

Resumen

Mediste cada candidato elegible con una sola escala definida por el catálogo, explicaste su distancia con diferencias reales y con signo en las unidades originales, y seleccionaste todos los empates sin redondear antes. Tus tres archivos representan una noche normal, menos candidatos de los pedidos y ningún candidato. La recomendación expresa similitud de contenido transparente, no una predicción de disfrute.