Einführung
An der Ergebniserfassung des Field Day treffen Versuche in unterschiedlicher Reihenfolge ein. Alex erzielt in drei Versuchen 80, 90 und 85 Punkte. Der Bestwert ist 90: Dies sind absolute Ergebnisse, keine zu addierenden Punkte. Eine zweite Person heißt ebenfalls Alex, daher können Namen die Teilnehmenden nicht identifizieren. Bei einem Gleichstand mit dem Ergebnis auf dem dritten Platz qualifizieren sich alle mit diesem Ergebnis.
Du speicherst wiederverwendbare Redis-Funktionen zur Erfassung und Qualifikation, verarbeitest das tatsächliche Versuchsprotokoll und veröffentlichst die tatsächlich qualifizierten Personen. Anschließend vervollständigst du einen kleinen Zeitlauf, bei dem niedrigere Werte besser sind. Beide Übungen verwenden vorbereitete synthetische Identitäten und ganzzahlige Ergebnisse. Grundlegende Terminalnavigation sowie Redis-Verbindungen/GET/SET sind Voraussetzungen; Sorted Sets und die bereitgestellten Python-Funktionsvorlagen werden vor ihrer Verwendung erklärt. Redis und Terminal sind bereit.
Arbeite in /home/labex/project/score-desk. Erstelle main.py und practice.py; aktualisiere nur score:main:best, score:practice:best und temporäre Schlüssel mit dem Präfix score:demo:. Bewahre die ursprünglichen CSV-Protokolle, die Hashes der Teilnehmendenlisten, die vorherige Runde und alle bereitgestellten Werkzeuge. Das Hauptergebnis einer bereits erfassten Person mit 88 Punkten und das Übungsergebnis einer bereits erfassten Person mit 72 Sekunden müssen erhalten bleiben, obwohl ihre Versuche in den neuen Protokollen fehlen.
Ein einzelner Versuch muss den Bestwert seiner Person auch dann sicher aktualisieren, wenn sich eine weitere Übermittlung damit überschneidet. Die Qualifikation erfolgt nach dem Ende der Übermittlungen; ihre beiden Lesezugriffe sind keine Momentaufnahme eines sich verändernden Wettbewerbs. Ergebnisse sind Ganzzahlen von 1 bis 1000000. Diese Übung implementiert weder Ergebniseinsprüche noch das Löschen eines fehlerhaften Bestwerts, Betrugserkennung, Zeitmessung mit Nachkommastellen oder Persistenz/Failover.
Den besten absoluten Versuch beibehalten
In diesem Schritt behältst du für jede Person den höchsten absoluten Versuch bei, ohne ihn von einem verspäteten niedrigeren Ergebnis ersetzen zu lassen.
Verwende Terminal, beginnend in der normalen Shell. Das bereitgestellte Programm desk liest das tatsächliche Protokoll und die Datenbank. Es ruft deine gespeicherten Funktionen unverändert auf; es berechnet oder repariert keinen korrekten Bestwert für dich. ./ bedeutet, eine Datei im aktuellen Verzeichnis auszuführen. Wechsle das Verzeichnis und prüfe das Protokoll und die Teilnehmendenliste:
cd /home/labex/project/score-desk
./desk attempts main
./redis HGETALL score:main:names
./desk data main
player_id ist eine stabile Identität; score ist die absolute Punktzahl eines Versuchs. Die IDs p01 und p02 gehören beide zu Personen namens Alex. Das vorbereitete Haupt-Sorted-Set enthält derzeit nur p90 mit dem Ergebnis 88. Dieser Eintrag muss erhalten bleiben. Redis speichert Namen separat in einem Hash, einer Zuordnung von Personen-ID zu Name. HGETALL liest diese Zuordnung; du musst sie nicht bearbeiten.
Ein Sorted Set speichert jedes Mitglied einmal mit einem Zahlenwert. Hier ist das Mitglied eine Personen-ID. Redis ordnet Mitglieder nach dem Zahlenwert. Einfaches ZADD key score member fügt ein neues Mitglied hinzu oder ersetzt dessen vorhandenen Wert. Verwende einen entbehrlichen Demoschlüssel, um zu sehen, warum das zuletzt eingetroffene Ergebnis nicht unbedingt das beste ist:
./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
Das endgültige Ergebnis ist 85, weil der letzte Befehl 90 überschrieben hat. ZSCORE liest den Wert eines Mitglieds; --raw fordert von der CLI einen einfachen Wert an. ZINCRBY würde die Versuche stattdessen addieren und aus diesen drei Werten 255 erzeugen. Diese Operation eignet sich für Erhöhungen, aber diese Versuche sind keine Erhöhungen.
ZADD mit GT fügt ein neues Mitglied hinzu oder ändert einen vorhandenen Wert nur dann, wenn der neue Wert größer ist. Vergleich und Aktualisierung erfolgen innerhalb eines einzigen Redis-Befehls. Ein separater clientseitiger Lesezugriff mit anschließendem unbedingtem Schreibzugriff kann mit einem anderen Aufruf in einen Wettlauf geraten. Probiere die atomare Bedingung aus:
./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
Das Ergebnis bleibt 90, auch nach dem doppelten Versuch. Standardmäßig zählt die Antwort von ZADD neue Mitglieder, daher kann 0 sowohl eine erfolgreiche Aktualisierung eines vorhandenen Mitglieds als auch einen ignorierten Versuch bedeuten. Das ist kein Fehler. Lies das tatsächliche Ergebnis, um diese Fälle zu unterscheiden. GT verhindert nicht das Einfügen einer neuen Person; das Hinzufügen von XX würde erste Versuche verhindern und wäre hier falsch.
Speichere eine wiederverwendbare Funktion, damit jede übermittelte Zeile derselben Regel folgt. Das bereitgestellte verarbeitende Programm übergibt einen Redis-Client, den tatsächlichen Schlüssel, die Personen-ID und das Ergebnis. Python-def definiert die Funktion; die Namen in Klammern empfangen diese Werte. Das eingerückte return sendet einen Befehl und gibt dessen tatsächliche Antwort zurück. Wörter in Anführungszeichen sind wörtliche Redis-Befehlsargumente; Namen ohne Anführungszeichen verwenden die Werte des Aufrufers. Es muss kein Python-Redis-Paket installiert werden: Der bereitgestellte Client sendet native Redis-Befehle.
Das folgende Here-Dokument schreibt die vollständige Datei. cat > main.py ersetzt diese Datei. Die Markierung PY in Anführungszeichen verhindert, dass die Shell den Inhalt interpretiert; das abschließende PY muss allein in seiner Zeile stehen. Bewahre die vier Leerzeichen vor return. Füge den gesamten Block in Terminal ein:
cat > main.py <<'PY'
def record(client, key, player, score):
return client.command("ZADD", key, "GT", score, player)
PY
Verarbeite das vorbereitete Protokoll über deine Funktion. Verarbeite es anschließend erneut und übermittle den verspäteten niedrigeren Versuch noch einmal:
./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 bleibt bei 90; die andere Person Alex, p02, bleibt ein separates Mitglied mit 90. Die vollständige Menge enthält außerdem p05=100, p03=95, p06=90, p04=70 und den früheren Eintrag p90=88. data zeigt abwechselnd IDs und Ergebniszeichenfolgen in aufsteigender Redis-Reihenfolge.
ZREVRANK liefert die nullbasierte Position eines Mitglieds in absteigender Reihenfolge: Die erste Position ist 0. Hier hat p01 Position 4, weil gleiche Ergebnisse eine umgekehrte byteweise ID-Reihenfolge verwenden. Diese Position disqualifiziert keine Person mit Gleichstand am Qualifikationsergebnis. Wir verwenden für die Qualifikation Ergebnisse statt einer festen Anzahl von IDs.
Vertippst du dich bei einem Dateinamen oder Befehl, lies den Fehler, prüfe mit ls die tatsächlichen Dateien und wiederhole den korrigierten Befehl. Ein Score desk error unterscheidet sich von einem erfolgreichen leeren Ergebnis. Hast du versehentlich eine falsche Bewertungsregel angewendet, korrigiere zuerst main.py, prüfe das ursprüngliche Protokoll und stelle mit gewöhnlichem ZADD den tatsächlichen Bestwert jeder betroffenen Person wieder her; zum Beispiel ./redis ZADD score:main:best 90 p01. Bearbeite weder Protokoll noch Teilnehmendenliste, lösche nicht die Runde und verwerfe nicht die bereits erfasste Person. Führe nach dem Prüfen der tatsächlichen Ergebnisse die Schrittprüfung aus.
Alle Personen an der Qualifikationsgrenze einschließen
In diesem Schritt überführst du eine Positionsgrenze in eine inklusive Ergebnisgrenze, damit sich jede Person mit Gleichstand qualifiziert.
Bleibe in Terminal und im score-desk-Verzeichnis. Für diesen Teil sind die Übermittlungen abgeschlossen. Die Regel lautet: die ersten drei Positionen plus alle Personen mit demselben Ergebnis wie an der dritten Position. Es gibt zwei getrennte Entscheidungen: dieses Ergebnis ermitteln und dann alle Personen einschließen, die es erreichen.
ZRANGE liest ein Sorted Set. Ohne Ergebnisoption sind seine Grenzen inklusive, nullbasierte Positionen. REV kehrt die Ergebnisreihenfolge um; WITHSCORES schließt den Wert jedes Mitglieds ein. Die dritte Position hat Index 2:
./redis --raw ZRANGE score:main:best 0 2 REV WITHSCORES
./redis --raw ZRANGE score:main:best 2 2 REV WITHSCORES
Der erste Befehl gibt nur p05, p03 und p06 zurück. Der zweite gibt p06 und 90 zurück: Die Ergebnisgrenze liegt bei 90. Bei drei Personen aufzuhören, würde p02 und p01 verlieren, die sich beide nach der angegebenen Gleichstandsregel qualifizieren.
Mit BYSCORE sind die Grenzen Ergebniswerte, keine Positionen. Gib in umgekehrter Reihenfolge die Obergrenze zuerst an. +inf bedeutet keine Obergrenze. Eine einfache Ergebnisgrenze schließt Gleichheit ein; eine Grenze, die mit ( beginnt, würde Gleichheit ausschließen und fälschlich alle Gleichstände an der Grenze verlieren. Lies die vollständige qualifizierte Menge:
./redis --raw ZRANGE score:main:best +inf 90 BYSCORE REV WITHSCORES
Alle fünf IDs erscheinen, geordnet als p05, p03, p06, p02, p01. Die ID-Reihenfolge bei gleichen Ergebnissen ist eine von Redis vorgegebene Anzeigekonvention, keine Regel zum Ausschluss von Personen mit Gleichstand oder zum Sortieren ihrer Namen.
Lass dies für jedes positive k funktionieren. Der Client gibt eine Python-Liste mit abwechselnden Zeichenfolgen zurück: Für eine einzelne Person an der Grenze ist sie ["p06", "90"]. Listenindex 0 ist die ID und Index 1 das Ergebnis. boundary[1] verwendet daher den tatsächlichen Wert aus Redis. k - 1 wandelt die angeforderte Position in einen nullbasierten Index um. Die Zuweisung = speichert diese Antwort in der lokalen Variablen boundary.
if not boundary prüft, ob die Liste leer ist. Ist k größer als die Anzahl der Personen, qualifizieren sich alle; lies die Positionen 0 bis -1, wobei -1 das letzte Mitglied bedeutet. Für eine leere Runde gibt dies ebenfalls eine ehrliche leere Liste zurück. Dieser Ausweichpfad verwendet das tatsächliche Redis-Set, keine Beispielantwort.
Hänge die nächste Funktion an main.py an. Hier hängt >> an, statt die vorhandene Erfassungsfunktion zu ersetzen. Python verwendet die zusätzliche Einrückung unter if, um dessen bedingte Rückgabe zu kennzeichnen:
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
Die bereitgestellte Aktion board ruft qualify auf und ergänzt dann Namen aus der tatsächlichen Teilnehmendenliste. Sie bewahrt deine zurückgegebenen Mitglieder, Werte und deren Reihenfolge. Sie filtert nicht erneut, ergänzt keinen fehlenden Gleichstand und ersetzt keine falsche Ergebnisgrenze durch eine korrekte. Probiere unterschiedliche Anfragen aus:
./desk board main 3
./desk board main 1
./desk board main 100
Die erste Anfrage gibt fünf Personenobjekte zurück. Beide Alex-Datensätze erscheinen mit unterschiedlichen player_id-Werten. Die zweite gibt nur p05 mit 100 zurück. Die letzte gibt alle sieben Personen zurück, einschließlich p90 mit 88 und p04 mit 70. Das Lesen einer Rangliste übermittelt keine neuen Versuche.
Die beiden Lesezugriffe dieser Funktion sind für eine abgeschlossene Runde vorgesehen. Sie garantieren keine einzelne konsistente Momentaufnahme, während weiterhin Übermittlungen stattfinden. Der einzelne bedingte Erfassungsbefehl ist atomar; das sind unterschiedliche Aussagen. Halte deine Erfassungsfunktion funktionsfähig und führe die Schrittprüfung aus.
Dieselben Regeln auf einen Zeitlauf anwenden
In diesem Schritt erstellst und verwendest du eine vollständige Erfassungs- und Qualifikationsmethode für eine unabhängige Übungsrunde, bei der niedrigere Werte besser sind.
Bleibe in Terminal und im selben Verzeichnis. Ein kurzer Übungslauf erfasst positive ganzzahlige Laufzeiten in Sekunden, daher sind niedrigere Werte besser. Sein unabhängiges Protokoll hat neue IDs; die bereits erfasste Person t90 hat schon 72 Sekunden. Prüfe es vor dem Schreiben:
./desk attempts practice
./redis HGETALL score:practice:names
./desk data practice
t01 hat Versuche von 70, 60 und 65 Sekunden. Der Bestwert ist 60, nicht 65. LT ändert einen vorhandenen Wert nur, wenn der neue niedriger ist; neue Mitglieder werden weiterhin hinzugefügt. Vergleiche dieselben drei Versuche auf einem temporären Schlüssel:
./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
Das Ergebnis bleibt 60. Die Überlegungen zur atomaren Aktualisierung und zur Sicherheit bei Duplikaten sind dieselben wie bei GT; nur die Richtung für bessere Werte ändert sich.
Für eine Rangliste, bei der niedrigere Werte besser sind, verwende aufsteigende Reihenfolge, indem du REV weglässt. Schließe an der Grenzposition k - 1 alle Personen mit einem Ergebnis kleiner oder gleich diesem Wert ein. Ein aufsteigender Ergebnisbereich beginnt mit -inf, also ohne Untergrenze. Speichere die vollständige unabhängige Vorlage in practice.py; sie verwendet exakt dieselben Funktionsschnittstellen wie die Hauptrunde:
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
Übermittle das Übungsprotokoll, prüfe die native Grenze und den inklusiven Bereich und frage dann das tatsächliche verarbeitende Ranglistenprogramm nach seinem Ergebnis:
./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
Die Grenze ist t01 mit 60. Die Rangliste der ersten zwei einschließlich Gleichständen enthält t03=50, t01=60, t02=60 und t04=60. Die gleichen Namen Sam gehören zu unterschiedlichen IDs. Die verspätete 65 kann 60 nicht ersetzen, und die erneute Verarbeitung des gesamten Protokolls verändert keinen Bestwert. Die große Grenze schließt alle sechs Laufenden ein, einschließlich t90=72. Die Qualifikation der fünf Personen in der Hauptrunde bleibt verfügbar.
Bei gleichen Zeiten sortiert Redis IDs byteweise aufsteigend; die Hauptrangliste mit hohen Punktzahlen verwendet wegen REV byteweise absteigende Reihenfolge. Keine der Konventionen schließt Personen mit gleichem Ergebnis aus. Für diese vorbereiteten ASCII-IDs ist die angegebene Reihenfolge stabil.
Du hast nun vollständige Erfassungs- und Qualifikationsmethoden für beide Richtungen. Bewahre beide Dateien, beide Runden, ursprüngliche Protokolle, Teilnehmendenlisten und Ergebnisse früherer Runden. Prüfungen verwenden deine unveränderten Funktionen mit anderen IDs, Werten, Eingangsreihenfolgen und Grenzen. Führe nach dem Prüfen der tatsächlichen Ergebnisse die Abschlussprüfung aus.
Zusammenfassung
Du hast stabile Personenidentitäten beibehalten, absolute Bestwerte mit atomaren bedingten Redis-Aktualisierungen gespeichert und die Bewertungsrichtung von der Anzeigereihenfolge getrennt. Du hast das tatsächliche k-te Ergebnis verwendet, um alle Gleichstände an der Qualifikationsgrenze einzuschließen, auch gleichnamige Personen, und frühere Ergebnisse bewahrt. Deine gespeicherten Funktionen funktionieren sowohl für Wettbewerbe mit hohen Punktzahlen als auch für Zeitläufe mit ganzzahligen Sekunden. Die Qualifikations-Lesezugriffe setzen voraus, dass keine Übermittlungen mehr stattfinden; Persistenz, Einsprüche und Momentaufnahmen im laufenden Betrieb sind separate Aufgaben.



