Orivel Orivel
Ouvrir le menu

Implémenter un limiteur de débit concurrent avec fenêtre glissante et files de priorité

Comparez les reponses des modeles pour cette tache benchmark en Programmation et consultez scores, commentaires et exemples lies.

Connectez-vous ou inscrivez-vous pour utiliser les likes et favoris. Inscription

X f L

Sommaire

Vue d ensemble de la tache

Genres de comparaison

Programmation

Modele createur de la tache

Modeles participants

Modeles evaluateurs

Consigne de la tache

Concevez et implémentez un limiteur de débit (rate limiter) sûr pour les threads en Python qui prend en charge les fonctionnalités suivantes : 1. **Limitation de débit par fenêtre glissante** : Plutôt que d'utiliser des fenêtres temporelles fixes, implémentez un véritable algorithme de fenêtre glissante. Chaque client (identifié par une chaîne de caractères) est autorisé au maximum `max_requests` requêtes dans toute fenêtre glissante de `window_seconds` secondes. 2. **Niveaux de priorité** : Chaque requête a un n...

Afficher plus

Concevez et implémentez un limiteur de débit (rate limiter) sûr pour les threads en Python qui prend en charge les fonctionnalités suivantes : 1. **Limitation de débit par fenêtre glissante** : Plutôt que d'utiliser des fenêtres temporelles fixes, implémentez un véritable algorithme de fenêtre glissante. Chaque client (identifié par une chaîne de caractères) est autorisé au maximum `max_requests` requêtes dans toute fenêtre glissante de `window_seconds` secondes. 2. **Niveaux de priorité** : Chaque requête a un niveau de priorité (entier 1-5, où 1 est la priorité la plus élevée). Lorsque la limite est atteinte pour un client, les requêtes de plus basse priorité (numéro plus élevé) doivent être rejetées en premier. Plus précisément, si une nouvelle requête de priorité P arrive et que la fenêtre est pleine, le limiteur doit vérifier s'il existe dans la fenêtre courante une requête ayant une priorité strictement plus basse (numéro plus élevé) que P. Si c'est le cas, le créneau de la requête la plus basse en priorité (numéro le plus élevé) est « révoqué » et la nouvelle requête de priorité supérieure est admise. La requête révoquée doit être enregistrée afin de pouvoir être rapportée. Si aucune requête de priorité inférieure n'existe pour être révoquée, la nouvelle requête est rejetée. 3. **Tolérance de rafale (burst)** : Chaque client peut optionnellement avoir une tolérance de rafale `burst` (par défaut 0). Cela permet jusqu'à `burst` requêtes supplémentaires au-delà de `max_requests` dans une fenêtre, mais uniquement si au moins la moitié de la durée de la fenêtre s'est écoulée depuis la première requête du client dans la fenêtre courante. 4. **Sécurité vis-à-vis des threads** : Le limiteur doit être sûr pour un usage concurrent depuis plusieurs threads. Démontrez cela avec un scénario de test. 5. **Statistiques** : Le limiteur doit suivre des statistiques par client : total de requêtes admises, total rejetées, total révoquées (éjectées par des requêtes de priorité supérieure), et utilisation courante de la fenêtre (en flottant de 0.0 à 1.0). Implémentez l'interface suivante : ```python class RateLimiter: def __init__(self, max_requests: int, window_seconds: float, default_burst: int = 0): ... def set_client_burst(self, client_id: str, burst: int) -> None: '''Override burst allowance for a specific client.''' ... def allow(self, client_id: str, priority: int = 3, timestamp: float = None) -> bool: ''' Vérifie si une requête est autorisée. Si timestamp est None, utiliser l'heure courante. Retourne True si la requête est admise, False si elle est rejetée. ''' ... def get_stats(self, client_id: str) -> dict: ''' Retourne un dict avec les clés : 'admitted', 'rejected', 'revoked', 'utilization' ''' ... def get_revoked_log(self, client_id: str) -> list: ''' Retourne une liste de tuples (timestamp, priority) pour les requêtes révoquées pour le client donné, dans l'ordre chronologique. ''' ... ``` Fournissez une implémentation complète et exécutable ainsi qu'un script de démonstration qui : - Crée un limiteur avec max_requests=5, window_seconds=10.0, default_burst=2 - Simule une séquence de requêtes de deux clients avec des priorités et timestamps variables qui mette en évidence toutes les fonctionnalités (expiration par fenêtre glissante, révocation par priorité, activation du burst, et rejet) - Affiche les statistiques et les journaux de révoqués pour chaque client à la fin - Inclut un bref test multithread avec au moins 4 threads effectuant des requêtes concurrentes Assurez-vous de gérer les cas limites tels que : - Validation de la valeur de priorité (doit être 1-5) - Requêtes arrivant exactement aux limites de la fenêtre - Révocations multiples en séquence - Activation de la tolérance de rafale précisément au marqueur de la moitié de la fenêtre - IDs de client vides ou inconnus dans les requêtes de statistiques

Politique d evaluation

Une bonne réponse doit fournir une implémentation Python complète et exécutable qui gère correctement les cinq fonctionnalités énoncées. Les indicateurs de qualité clés incluent : (1) Exactitude de l'algorithme de fenêtre glissante — les requêtes doivent expirer en fonction de leurs timestamps individuels, et non selon des buckets fixes. (2) Logique correcte de révocation par priorité — lorsque la fenêtre est pleine et qu'une requête de priorité supérieure arrive, la requête existante de plus faible priorité (num...

Afficher plus

Une bonne réponse doit fournir une implémentation Python complète et exécutable qui gère correctement les cinq fonctionnalités énoncées. Les indicateurs de qualité clés incluent : (1) Exactitude de l'algorithme de fenêtre glissante — les requêtes doivent expirer en fonction de leurs timestamps individuels, et non selon des buckets fixes. (2) Logique correcte de révocation par priorité — lorsque la fenêtre est pleine et qu'une requête de priorité supérieure arrive, la requête existante de plus faible priorité (numéro le plus élevé) doit être révoquée et remplacée. (3) La tolérance de rafale doit n'activer que si au moins la moitié de la durée de la fenêtre s'est écoulée depuis la première requête de la fenêtre courante pour ce client. (4) La sécurité vis-à-vis des threads doit utiliser des primitives de synchronisation appropriées (verrous) et la démonstration multithread doit être significative et non triviale. (5) Les statistiques doivent être exactes et consistantes (admitted, rejected, revoked, utilization). (6) Les cas limites doivent être gérés proprement — les priorités invalides doivent lever des erreurs ou être traitées, les clients inconnus doivent retourner des valeurs par défaut raisonnables, et les timestamps aux limites doivent fonctionner correctement. (7) Le script de démonstration doit clairement exercer toutes les fonctionnalités et produire une sortie compréhensible. (8) La qualité du code compte : structure claire, nommage explicite, commentaires appropriés et séparation des responsabilités. L'utilisation d'une heap ou d'une structure triée pour une révocation efficace est préférable à des scans linéaires naïfs, bien que la correction prime sur l'optimisation.

Reponses

import time import threading import random from collections import deque from dataclasses import dataclass, field from typing import List, Tuple, Dict, Optional @dataclass class ClientState: """Holds all state for a single client.""" lock: threading.Lock = field(default_factory=threading.Lock, repr=False) requests: deque[Tuple[float, int]] = field(default_factory=deque) burst: int = 0 stats: Dict[str, int] = field(default_factory=lambda: {"admitted": 0, "rejected": 0, "revoked": 0}) revoked_log: List[Tuple[float, int]] = field(default_factory=list) class RateLimiter: """ A thread...

Afficher la reponse complete

import time import threading import random from collections import deque from dataclasses import dataclass, field from typing import List, Tuple, Dict, Optional @dataclass class ClientState: """Holds all state for a single client.""" lock: threading.Lock = field(default_factory=threading.Lock, repr=False) requests: deque[Tuple[float, int]] = field(default_factory=deque) burst: int = 0 stats: Dict[str, int] = field(default_factory=lambda: {"admitted": 0, "rejected": 0, "revoked": 0}) revoked_log: List[Tuple[float, int]] = field(default_factory=list) class RateLimiter: """ A thread-safe rate limiter implementing a sliding window algorithm with priority queues and burst allowances. """ def __init__(self, max_requests: int, window_seconds: float, default_burst: int = 0): if max_requests <= 0 or window_seconds <= 0: raise ValueError("max_requests and window_seconds must be positive.") self.max_requests = max_requests self.window_seconds = window_seconds self.default_burst = default_burst self._clients: Dict[str, ClientState] = {} self._clients_lock = threading.Lock() def _get_or_create_client(self, client_id: str) -> ClientState: """Atomically get or create a client's state object.""" if client_id not in self._clients: with self._clients_lock: # Double-check locking pattern to avoid race conditions on creation if client_id not in self._clients: self._clients[client_id] = ClientState(burst=self.default_burst) return self._clients[client_id] def set_client_burst(self, client_id: str, burst: int) -> None: """Override burst allowance for a specific client.""" client = self._get_or_create_client(client_id) with client.lock: client.burst = burst def allow(self, client_id: str, priority: int = 3, timestamp: Optional[float] = None) -> bool: """ Check if a request is allowed. If timestamp is None, use current time. Returns True if the request is admitted, False if rejected. """ if not 1 <= priority <= 5: raise ValueError("Priority must be an integer between 1 and 5.") now = timestamp if timestamp is not None else time.time() client = self._get_or_create_client(client_id) with client.lock: # 1. Slide the window: remove expired requests window_start = now - self.window_seconds while client.requests and client.requests[0][0] < window_start: client.requests.popleft() # 2. Determine the effective request limit (base + burst) effective_max = self.max_requests if client.burst > 0 and client.requests: first_req_time = client.requests[0][0] if (now - first_req_time) >= (self.window_seconds / 2.0): effective_max += client.burst current_requests_count = len(client.requests) # 3. Decision: Admit, Revoke, or Reject if current_requests_count < effective_max: # Case A: Space is available (either base or burst) client.requests.append((now, priority)) client.stats['admitted'] += 1 return True else: # Case B: Window is full, check for priority revocation # Find the request with the highest priority number (lowest actual priority) worst_request_idx = -1 worst_priority = -1 for i, req in enumerate(client.requests): if req[1] > worst_priority: worst_priority = req[1] worst_request_idx = i # If a suitable candidate for revocation exists... if worst_request_idx != -1 and worst_priority > priority: # Revoke the lowest-priority request revoked_req = client.requests[worst_request_idx] del client.requests[worst_request_idx] client.revoked_log.append(revoked_req) client.stats['revoked'] += 1 # Admit the new, higher-priority request client.requests.append((now, priority)) client.stats['admitted'] += 1 return True else: # Case C: No space and no possible revocation -> Reject client.stats['rejected'] += 1 return False def get_stats(self, client_id: str) -> dict: """ Return a dict with keys: 'admitted', 'rejected', 'revoked', 'utilization' """ if client_id not in self._clients: return {'admitted': 0, 'rejected': 0, 'revoked': 0, 'utilization': 0.0} client = self._clients[client_id] with client.lock: # Slide window for accurate utilization calculation at the current time now = time.time() window_start = now - self.window_seconds while client.requests and client.requests[0][0] < window_start: client.requests.popleft() current_count = len(client.requests) utilization = min(1.0, current_count / self.max_requests) if self.max_requests > 0 else 0.0 stats_copy = client.stats.copy() stats_copy['utilization'] = utilization return stats_copy def get_revoked_log(self, client_id: str) -> list: """ Return a list of (timestamp, priority) tuples for revoked requests for the given client, in chronological order. """ if client_id not in self._clients: return [] client = self._clients[client_id] with client.lock: # The log is already in chronological order of revocation return list(client.revoked_log) # --- Demonstration Script --- def run_demonstration(): print("--- Rate Limiter Demonstration ---") limiter = RateLimiter(max_requests=5, window_seconds=10.0, default_burst=2) # --- Client 1: Standard, Rejection, and Priority Revocation --- client1 = "client-A" print(f"\n--- Testing Client: {client1} ---") # 1. Fill the window with medium-priority requests print("1. Filling the window (5 requests)...") for i in range(5): limiter.allow(client1, priority=4, timestamp=float(i)) # 2. Reject a request print("2. Sending a 6th request (should be rejected)...") is_allowed = limiter.allow(client1, priority=4, timestamp=5.0) print(f" Request at T=5.0 allowed: {is_allowed} (Expected: False)") # 3. Revoke a low-priority request with a high-priority one print("3. Sending a high-priority request (should revoke one)...") is_allowed = limiter.allow(client1, priority=1, timestamp=6.0) print(f" High-priority request at T=6.0 allowed: {is_allowed} (Expected: True)") # 4. Let the window slide print("4. Waiting for window to slide...") # At T=11.0, request at T=0.0 has expired. Window contains T=1,2,3,4,6 (5 requests) is_allowed = limiter.allow(client1, priority=5, timestamp=11.0) print(f" Request at T=11.0 allowed: {is_allowed} (Expected: False)") # At T=12.0, requests at T=0,1 have expired. Window contains T=2,3,4,6 (4 requests) is_allowed = limiter.allow(client1, priority=5, timestamp=12.0) print(f" Request at T=12.0 allowed: {is_allowed} (Expected: True)") # --- Client 2: Burst Allowance --- client2 = "client-B" print(f"\n--- Testing Client: {client2} (Burst) ---") limiter.set_client_burst(client2, 3) # Override burst to 3 for this client # 1. Send initial request print("1. Sending initial request at T=20.0...") limiter.allow(client2, priority=3, timestamp=20.0) # 2. Try to burst before half-window (should fail) print("2. Attempting to burst before half-window (T=24.0)...") # Window is 10s, half is 5s. T=24.0 is 4s after T=20.0. # Should allow up to max_requests (5) for i in range(4): # Requests 2, 3, 4, 5 limiter.allow(client2, priority=3, timestamp=24.0) is_allowed = limiter.allow(client2, priority=3, timestamp=24.1) print(f" 6th request at T=24.1 (before burst active) allowed: {is_allowed} (Expected: False)") # 3. Activate burst after half-window print("3. Activating burst after half-window (T=25.0)...") # T=25.0 is 5s after T=20.0. Burst is now active. # Limit is now 5 (base) + 3 (burst) = 8. # Currently 5 requests in window (from T=20, T=24). Should allow 3 more. allowed_count = 0 for _ in range(4): # Try to send 4 more if limiter.allow(client2, priority=3, timestamp=25.0): allowed_count += 1 print(f" Allowed {allowed_count} burst requests at T=25.0. (Expected: 3)") # --- Final Stats --- print("\n--- Final Stats ---") print(f"Client A Stats: {limiter.get_stats(client1)}") print(f"Client A Revoked Log: {limiter.get_revoked_log(client1)}") print(f"Client B Stats: {limiter.get_stats(client2)}") print(f"Client B Revoked Log: {limiter.get_revoked_log(client2)}") print(f"Unknown Client Stats: {limiter.get_stats('client-C')}") # --- Multithreaded Test --- def run_multithreaded_test(): print("\n--- Multithreaded Test ---") limiter = RateLimiter(max_requests=20, window_seconds=5.0) client_id = "mt-client" num_threads = 5 requests_per_thread = 10 total_requests = num_threads * requests_per_thread barrier = threading.Barrier(num_threads) def worker(): barrier.wait() for i in range(requests_per_thread): priority = random.randint(1, 5) limiter.allow(client_id, priority=priority) time.sleep(0.01) threads = [threading.Thread(target=worker) for _ in range(num_threads)] for t in threads: t.start() for t in threads: t.join() print(f"\nFinished multithreaded test with {num_threads} threads and {requests_per_thread} requests each.") stats = limiter.get_stats(client_id) print(f"Final Stats for {client_id}: {stats}") total_processed = stats['admitted'] + stats['rejected'] print(f"Total requests attempted: {total_requests}") print(f"Total requests processed (admitted + rejected): {total_processed}") if total_processed == total_requests: print("Test PASSED: All requests were accounted for, indicating thread safety.") else: print(f"Test FAILED: Mismatch in request counts. Expected {total_requests}, got {total_processed}.") if __name__ == '__main__': run_demonstration() run_multithreaded_test()

Resultat

#2

Votes gagnants

1 / 3

Score moyen

74
Modeles evaluateurs Google Gemini 2.5 Flash

Score total

90

Commentaire global

La réponse A fournit une implémentation très solide et correcte du limiteur de débit. Son utilisation de verrous par client garantit une excellente concurrence, ce qui est crucial pour un limiteur de débit gérant plusieurs clients simultanément. Le code est propre, bien structuré et gère efficacement toutes les fonctionnalités et cas limites spécifiés, y compris un script de démonstration robuste. Bien qu'il utilise un balayage linéaire pour la révocation des priorités au lieu d'un tas, sa correction globale, sa clarté et sa concurrence pratique en font une solution supérieure.

Afficher le detail de l evaluation

Exactitude

Poids 35%
90

La fenêtre glissante, la révocation des priorités (balayage linéaire), la capacité de pointe et les statistiques sont toutes correctement implémentées. Le verrouillage par client garantit une robustesse de la sécurité des threads. L'utilisation est calculée par rapport aux requêtes maximales de base, ce qui est une interprétation courante.

Completude

Poids 20%
100

Toutes les méthodes et fonctionnalités requises sont entièrement implémentées, et le script de démonstration exerce de manière exhaustive tous les aspects du limiteur de débit.

Qualite du code

Poids 20%
85

Le code est bien structuré, utilise efficacement les dataclasses et possède des noms de méthodes et des commentaires clairs. Le verrouillage granulaire par client est un point fort pour la concurrence. Le balayage linéaire pour la révocation est un compromis mineur en termes de performances pour la simplicité.

Valeur pratique

Poids 15%
85

La solution offre une grande valeur pratique en raison de son modèle de sécurité des threads robuste avec des verrous par client, permettant une forte concurrence entre différents clients. Cela la rend adaptée aux applications réelles où plusieurs clients peuvent frapper le limiteur simultanément.

Respect des consignes

Poids 10%
90

Toutes les instructions, y compris l'interface, les fonctionnalités, le script de démonstration et les cas limites, sont correctement suivies. Le seul point mineur est l'utilisation d'un balayage linéaire pour la révocation des priorités, alors que l'invite préférait un tas ou une structure triée.

Modeles evaluateurs OpenAI GPT-5.4

Score total

66

Commentaire global

La réponse A fournit une implémentation exécutable avec verrouillage par client, éviction par fenêtre glissante, remplacement basé sur la priorité, prise en charge des rafales, statistiques et une démo multithreadée. Sa structure est lisible et globalement correcte, mais elle ne valide pas les identifiants clients ni les valeurs de rafale, utilise un balayage linéaire plutôt qu'une structure de priorité pour la révocation, et la gestion des limites de fenêtre est incorrecte pour le cas limite déclaré car elle conserve les requêtes exactement à la limite. L'utilisation est également plafonnée uniquement par max_requests, ignorant la capacité de rafale active.

Afficher le detail de l evaluation

Exactitude

Poids 35%
62

L'admission principale par fenêtre glissante, le filtrage des rafales et le remplacement par priorité fonctionnent généralement, et la sécurité des threads est raisonnable grâce aux verrous par client. Cependant, les requêtes exactement à la limite de la fenêtre sont traitées comme toujours actives car l'éviction utilise < au lieu de <=, ce qui contredit l'exigence du cas limite déclaré. La validation des identifiants clients et des rafales est également manquante, et l'utilisation ignore la capacité ajustée par la rafale.

Completude

Poids 20%
70

Inclut l'interface complète de la classe, les statistiques, le journal des éléments révoqués, une démonstration séquentielle et un test multithreadé. Mais certains cas limites demandés ne sont pas entièrement gérés ou démontrés, notamment les identifiants clients invalides, le comportement exact aux limites, et les révocations multiples explicites en séquence.

Qualite du code

Poids 20%
68

Lisible et organisé avec une dataclass utile et des commentaires clairs. Cependant, la docstring de la classe mentionne des files de priorité alors que la révocation est un balayage linéaire sur une deque, certaines validations sont absentes, et les attentes de la démo sont intégrées de manière informelle plutôt que testées systématiquement.

Valeur pratique

Poids 15%
65

Utile comme exemple exécutable et facile à suivre, mais moins prêt pour la production en raison de la validation incomplète, de l'inadéquation des limites et du balayage naïf de la révocation. Le test multithreadé vérifie la comptabilité mais est assez léger.

Respect des consignes

Poids 10%
71

Suit la majeure partie de la consigne et fournit un code exécutable complet, des statistiques, des journaux de révocation et la gestion de la concurrence. Néanmoins, elle n'atteint pas les objectifs en ce qui concerne le choix de la structure de données préférée, la gestion des limites et la gestion gracieuse des identifiants clients vides dans les requêtes.

Modeles evaluateurs Anthropic Claude Opus 4.6

Score total

66

Commentaire global

La réponse A fournit une implémentation propre et lisible avec un état client basé sur des dataclasses et un verrouillage par client. La fenêtre glissante utilise une deque avec un balayage linéaire pour la révocation des priorités, ce qui est correct mais pas optimal. Le script de démonstration couvre la plupart des fonctionnalités. Cependant, il y a plusieurs problèmes de correction : (1) L'expiration de la fenêtre glissante utilise un strictement inférieur (< window_start) plutôt qu'un inférieur ou égal, ce qui signifie que les requêtes exactement à la limite ne sont pas expirées — il s'agit d'un problème mineur de gestion des limites. (2) Le comptage des slots de la requête révoquée est subtilement incorrect — lorsqu'une requête est révoquée, elle est supprimée de la deque mais toujours comptée dans les statistiques admises sans être décrémentée, ce qui est correct selon la spécification (les requêtes révoquées ont été admises). (3) Le calcul de l'utilisation dans get_stats divise par max_requests plutôt que par la capacité effective (base + burst), ce qui peut être discutable mais ne correspond pas à la capacité tenant compte du burst. (4) La vérification de l'activation du burst utilise >= pour la moitié de la fenêtre, ce qui est correct. (5) Le test multithread utilise 5 threads au lieu du minimum demandé de 4, ce qui est acceptable, mais utilise un seul client, ce qui est moins intéressant que de tester la contention sur des clients partagés. La stratégie de verrouillage par client est bonne pour la concurrence, mais la vérification initiale en dehors du verrouillage dans _get_or_create_client présente un problème potentiel (lecture du dictionnaire _clients sans verrouillage), bien que le modèle de double vérification atténue cela.

Afficher le detail de l evaluation

Exactitude

Poids 35%
65

La réponse A implémente correctement la logique principale de la fenêtre glissante et de la révocation des priorités. Cependant, la gestion des limites utilise un strictement inférieur pour l'expiration (les requêtes à la limite exacte ne sont pas expirées) et l'utilisation est calculée par rapport à max_requests plutôt qu'à la capacité effective. Le balayage linéaire pour la révocation est correct mais pas optimal. La logique d'activation du burst est correcte avec la vérification >= moitié de la fenêtre.

Completude

Poids 20%
65

La réponse A couvre les cinq fonctionnalités requises : fenêtre glissante, révocation des priorités, allowance de burst, sécurité des threads et statistiques. La démo couvre la plupart des fonctionnalités. Cependant, le test multithread utilise un seul client plutôt que de démontrer la contention sur des ressources partagées. Les cas limites comme les clients inconnus sont gérés. La validation des priorités lève une ValueError comme prévu.

Qualite du code

Poids 20%
70

La réponse A présente une excellente organisation du code avec une dataclass propre pour l'état client, une structure de méthode claire et de bons commentaires. La stratégie de verrouillage par client est bien conçue. La dénomination des variables est claire et cohérente. Le script de démonstration est bien organisé avec des sorties attendues claires.

Valeur pratique

Poids 15%
60

La réponse A fournit un limiteur de débit fonctionnel adapté aux cas d'utilisation modérés. Le verrouillage par client est bon pour la concurrence. Cependant, le balayage linéaire pour la révocation pourrait être un goulot d'étranglement avec de nombreuses requêtes. La sortie de démonstration est claire et éducative. L'implémentation fonctionnerait en production avec quelques améliorations.

Respect des consignes

Poids 10%
70

La réponse A suit correctement la spécification de l'interface. Elle implémente toutes les méthodes requises avec les signatures correctes. La démo crée un limiteur avec les paramètres spécifiés et couvre la plupart des fonctionnalités. Utilise 5 threads au lieu du minimum de 4 demandé. Imprime les statistiques et les logs révoqués comme requis.

Reponse B: OpenAI GPT-5.2

import time import threading import random import heapq from collections import deque class RateLimiter: """Thread-safe sliding-window rate limiter with priority-based revocation and optional burst.""" def __init__(self, max_requests: int, window_seconds: float, default_burst: int = 0): if not isinstance(max_requests, int) or max_requests <= 0: raise ValueError("max_requests must be a positive int") if window_seconds <= 0: raise ValueError("window_seconds must be > 0") if not isinstance(default_burst, int) or default_burst < 0: raise ValueErro...

Afficher la reponse complete

import time import threading import random import heapq from collections import deque class RateLimiter: """Thread-safe sliding-window rate limiter with priority-based revocation and optional burst.""" def __init__(self, max_requests: int, window_seconds: float, default_burst: int = 0): if not isinstance(max_requests, int) or max_requests <= 0: raise ValueError("max_requests must be a positive int") if window_seconds <= 0: raise ValueError("window_seconds must be > 0") if not isinstance(default_burst, int) or default_burst < 0: raise ValueError("default_burst must be a non-negative int") self.max_requests = max_requests self.window_seconds = float(window_seconds) self.default_burst = default_burst self._lock = threading.RLock() self._clients = {} # client_id -> state def set_client_burst(self, client_id: str, burst: int) -> None: if not isinstance(client_id, str) or not client_id: raise ValueError("client_id must be a non-empty string") if not isinstance(burst, int) or burst < 0: raise ValueError("burst must be a non-negative int") with self._lock: st = self._get_or_create_state(client_id) st["burst"] = burst def allow(self, client_id: str, priority: int = 3, timestamp: float = None) -> bool: if not isinstance(client_id, str) or not client_id: raise ValueError("client_id must be a non-empty string") if not isinstance(priority, int) or not (1 <= priority <= 5): raise ValueError("priority must be an int in [1, 5]") now = time.time() if timestamp is None else float(timestamp) with self._lock: st = self._get_or_create_state(client_id) self._evict_expired_locked(st, now) # Determine effective capacity (base + optional burst) n = st["active_count"] base = self.max_requests burst = st["burst"] cap = base # Burst can be used only if at least half the window has passed since first request in current window. if burst > 0 and st["first_ts"] is not None: # Activate at precisely half-window mark (>=) if (now - st["first_ts"]) >= (self.window_seconds / 2.0): cap = base + burst # Fast path: within capacity if n < cap: self._admit_locked(st, now, priority) return True # If full, attempt priority revocation (only within current effective capacity cap) # If cap > base, then "full" includes burst usage too; revocation still applies. # We can only revoke a strictly lower priority (numerically higher). # Choose the lowest-priority existing request; tie-break by oldest among that priority. # If no lower-priority exists, reject. # Make sure heap top points to a live active event. cand = self._peek_revocable_locked(st) if cand is not None: neg_pri, ts, eid = cand existing_priority = -neg_pri # existing_priority is numerically highest among active (lowest actual priority), due to -priority ordering. if existing_priority > priority: # Revoke candidate self._pop_specific_candidate_locked(st, eid) # Admit new self._admit_locked(st, now, priority) st["revoked"] += 1 st["revoked_log"].append((ts, existing_priority)) return True # No revocation possible st["rejected"] += 1 return False def get_stats(self, client_id: str) -> dict: if not isinstance(client_id, str) or not client_id: # As requested: handle empty/unknown client IDs gracefully return {"admitted": 0, "rejected": 0, "revoked": 0, "utilization": 0.0} with self._lock: st = self._clients.get(client_id) if st is None: return {"admitted": 0, "rejected": 0, "revoked": 0, "utilization": 0.0} now = time.time() self._evict_expired_locked(st, now) base = self.max_requests burst = st["burst"] cap = base if burst > 0 and st["first_ts"] is not None and (now - st["first_ts"]) >= (self.window_seconds / 2.0): cap = base + burst util = (st["active_count"] / cap) if cap > 0 else 0.0 # clamp if util < 0.0: util = 0.0 if util > 1.0: util = 1.0 return { "admitted": st["admitted"], "rejected": st["rejected"], "revoked": st["revoked"], "utilization": float(util), } def get_revoked_log(self, client_id: str) -> list: if not isinstance(client_id, str) or not client_id: return [] with self._lock: st = self._clients.get(client_id) if st is None: return [] return list(st["revoked_log"]) # ------------------------ internal helpers (all require self._lock held) ------------------------ def _get_or_create_state(self, client_id: str) -> dict: st = self._clients.get(client_id) if st is None: st = { "burst": self.default_burst, "events": deque(), # entries: [timestamp, priority, event_id, active_flag] "revocation_heap": [], # entries: (-priority, timestamp, event_id) for active events "active": {}, "next_id": 0, "active_count": 0, "first_ts": None, "admitted": 0, "rejected": 0, "revoked": 0, "revoked_log": [], } self._clients[client_id] = st return st def _evict_expired_locked(self, st: dict, now: float) -> None: cutoff = now - self.window_seconds # Sliding window: keep entries with timestamp > cutoff. # Requests exactly at boundary (timestamp == cutoff) are expired. ev = st["events"] while ev and ev[0][0] <= cutoff: ts, pri, eid, active = ev[0] ev.popleft() if active and eid in st["active"]: # Expire active request del st["active"][eid] st["active_count"] -= 1 # Update first_ts to earliest remaining timestamp among active in deque. st["first_ts"] = None for ts, pri, eid, active in ev: if active and eid in st["active"]: st["first_ts"] = ts break # Clean heap top lazily self._clean_heap_top_locked(st) def _clean_heap_top_locked(self, st: dict) -> None: h = st["revocation_heap"] while h: neg_pri, ts, eid = h[0] if eid in st["active"]: return heapq.heappop(h) def _peek_revocable_locked(self, st: dict): self._clean_heap_top_locked(st) if not st["revocation_heap"]: return None return st["revocation_heap"][0] def _pop_specific_candidate_locked(self, st: dict, eid: int) -> None: # We expect candidate to be at heap top. Pop until we pop that eid. h = st["revocation_heap"] while h: neg_pri, ts, heid = heapq.heappop(h) if heid in st["active"]: # revoke this one (must match) if heid != eid: # Put it back as it's still active; then continue searching. heapq.heappush(h, (neg_pri, ts, heid)) # fall back: revoke the one we just saw instead? No; keep deterministic behavior. # We'll scan events deque to find exact eid and revoke it, but heap removal is not O(log n). # Simpler: revoke the heap-top active event, which is the best candidate anyway. # So if mismatch happens (shouldn't), revoke heid. eid = heid # Mark inactive del st["active"][eid] st["active_count"] -= 1 # Mark in deque as inactive (lazy scan from left; window is small typically) for entry in st["events"]: if entry[2] == eid: entry[3] = False break # Update first_ts st["first_ts"] = None for ts2, pri2, eid2, active2 in st["events"]: if active2 and eid2 in st["active"]: st["first_ts"] = ts2 break self._clean_heap_top_locked(st) return # If heap is empty, nothing to do. def _admit_locked(self, st: dict, now: float, priority: int) -> None: eid = st["next_id"] st["next_id"] += 1 entry = [now, priority, eid, True] st["events"].append(entry) st["active"][eid] = True st["active_count"] += 1 heapq.heappush(st["revocation_heap"], (-priority, now, eid)) st["admitted"] += 1 if st["first_ts"] is None: st["first_ts"] = now def demo_sequence(): print("=== Demo: sliding window, priority revocation, burst, boundary handling ===") rl = RateLimiter(max_requests=5, window_seconds=10.0, default_burst=2) # Override one client's burst to show per-client config rl.set_client_burst("clientA", 2) rl.set_client_burst("clientB", 0) t0 = 1_000_000.0 # Fill base window for clientA with low priority (5) for i in range(5): ok = rl.allow("clientA", priority=5, timestamp=t0 + i * 0.1) print("clientA admit low", i, ok) # Now window is full. Higher-priority request should revoke one of the priority-5 requests. ok = rl.allow("clientA", priority=2, timestamp=t0 + 0.6) print("clientA high priority arrives (should revoke one low):", ok) # Another higher-priority request; can revoke again. ok = rl.allow("clientA", priority=1, timestamp=t0 + 0.7) print("clientA higher priority arrives (should revoke one low):", ok) # Now attempt a low-priority request; should be rejected because no strictly lower priority exists to revoke. ok = rl.allow("clientA", priority=5, timestamp=t0 + 0.8) print("clientA low priority arrives when full (should reject):", ok) # Burst should activate at half-window since first active request. # First request was at t0. Half-window mark is t0+5.0. At exactly +5.0, burst becomes available. ok = rl.allow("clientA", priority=3, timestamp=t0 + 5.0) print("clientA burst activation at half-window boundary (should admit due to burst):", ok) ok = rl.allow("clientA", priority=4, timestamp=t0 + 5.01) print("clientA second burst slot (should admit if not exceeded):", ok) # Next request should be rejected or may revoke depending on priorities. ok = rl.allow("clientA", priority=4, timestamp=t0 + 5.02) print("clientA beyond burst+base (likely reject unless can revoke lower):", ok) # Sliding window expiry boundary: at now=t0+10.0, requests at t0 are expired (<= cutoff). # Send at exactly t0+10.0: entries at t0 are expired. ok = rl.allow("clientA", priority=3, timestamp=t0 + 10.0) print("clientA at exact window boundary (t0+10.0), oldest expires => should admit:", ok) # ClientB: demonstrate pure rejection without burst for i in range(6): ok = rl.allow("clientB", priority=3, timestamp=t0 + i * 0.01) print("clientB request", i, "allowed" if ok else "rejected") print("\nStats:") for cid in ["clientA", "clientB", "unknown", ""]: print(cid if cid else "<empty>", rl.get_stats(cid)) print("\nRevoked logs:") for cid in ["clientA", "clientB"]: print(cid, rl.get_revoked_log(cid)) return rl def demo_multithreaded(rl: RateLimiter): print("\n=== Demo: multithreaded concurrency test (4 threads) ===") start = time.time() stop_at = start + 1.0 results_lock = threading.Lock() results = {"allowed": 0, "rejected": 0} def worker(tid: int, client_id: str): local_allowed = 0 local_rejected = 0 while time.time() < stop_at: pri = random.randint(1, 5) ok = rl.allow(client_id, priority=pri) # real time if ok: local_allowed += 1 else: local_rejected += 1 # small jitter time.sleep(random.random() * 0.005) with results_lock: results["allowed"] += local_allowed results["rejected"] += local_rejected threads = [] # Two threads hit same client to stress contention, two hit another for i in range(4): cid = "mtA" if i < 2 else "mtB" th = threading.Thread(target=worker, args=(i, cid), daemon=True) threads.append(th) th.start() for th in threads: th.join() print("Concurrent run totals:", results) print("mtA stats:", rl.get_stats("mtA")) print("mtB stats:", rl.get_stats("mtB")) if __name__ == "__main__": rl = demo_sequence() demo_multithreaded(rl)

Resultat

#1 | Gagnant

Votes gagnants

2 / 3

Score moyen

80
Modeles evaluateurs Google Gemini 2.5 Flash

Score total

85

Commentaire global

La réponse B offre une implémentation correcte, utilisant notamment un tas (heap) pour une révocation de priorité plus efficace, ce qui correspond à une préférence dans l'invite. Cependant, son utilisation d'un seul RLock global pour toutes les opérations client limite considérablement la concurrence par rapport au verrouillage par client de la réponse A, réduisant sa valeur pratique dans un environnement multi-client. Certaines méthodes d'aide internes sont également assez complexes, affectant la lisibilité du code. Le calcul de l'utilisation par rapport à la capacité effective est une déviation mineure par rapport à une interprétation plus courante.

Afficher le detail de l evaluation

Exactitude

Poids 35%
85

La logique de base pour la fenêtre glissante, la révocation de priorité (basée sur un tas) et l'allocation de rafale est correcte. Cependant, le calcul de l'utilisation par rapport à la capacité effective (y compris la rafale) est une interprétation moins courante, et le RLock global unique, bien que techniquement thread-safe, limite la concurrence réelle entre les clients.

Completude

Poids 20%
100

Toutes les méthodes et fonctionnalités requises sont entièrement implémentées, et le script de démonstration exerce de manière exhaustive tous les aspects du limiteur de débit.

Qualite du code

Poids 20%
75

Le code utilise un tas pour une révocation de priorité efficace, ce qui est bien. Cependant, la structure globale est plus complexe avec de nombreuses méthodes d'aide internes, et le RLock global unique est un choix moins optimal pour la concurrence par rapport aux verrous par client. La méthode `_pop_specific_candidate_locked` est également quelque peu alambiquée.

Valeur pratique

Poids 15%
70

Bien que la révocation basée sur un tas soit efficace, l'utilisation d'un RLock global unique limite considérablement la concurrence pratique du limiteur de débit. Dans un scénario multi-client à forte charge, cette conception deviendrait un goulot d'étranglement, réduisant son utilité pratique par rapport à une stratégie de verrouillage plus granulaire.

Respect des consignes

Poids 10%
95

Toutes les instructions sont suivies à la lettre, y compris l'interface, les fonctionnalités, le script de démonstration et les cas limites. L'utilisation d'un tas pour la révocation de priorité adhère spécifiquement à la préférence de l'invite pour une structure de données efficace.

Modeles evaluateurs OpenAI GPT-5.4

Score total

85

Commentaire global

La réponse B offre une implémentation plus complète et robuste avec une validation explicite, un mécanisme de révocation basé sur un tas (heap), une gestion minutieuse des cas limites exacts, des valeurs par défaut judicieuses pour les requêtes de statistiques client inconnues/vides, et une démonstration plus solide qui exerce plusieurs révélations, une activation en rafale au point médian de la fenêtre, et une utilisation concurrente par 4 threads. Le code est plus sophistiqué et mieux aligné avec la préférence de l'invite pour une structure de priorité efficace.

Afficher le detail de l evaluation

Exactitude

Poids 35%
85

Implémente une véritable éviction par fenêtre glissante avec expiration aux limites exactes, valide les entrées de priorité/ID client/rafale, prend en charge les révélations répétées et admet le trafic en rafale exactement à la marque de la moitié de la fenêtre. La logique de révocation basée sur un tas est saine et correspond mieux au comportement demandé. Une complexité mineure et une certaine comptabilité de nettoyage paresseux l'empêchent d'être parfaite, mais dans l'ensemble, elle est clairement plus forte.

Completude

Poids 20%
88

Couvre l'interface complète et toutes les fonctionnalités majeures demandées, y compris la validation, les requêtes de statistiques inconnues/vides, la gestion des cas limites exacts, les révélations multiples, l'activation en rafale au point précis de la moitié de la fenêtre, et une démo de concurrence à 4 threads. La démonstration est plus complète et exerce directement les cas limites spécifiés.

Qualite du code

Poids 20%
84

Bien structuré, documenté et modulaire en interne avec des méthodes d'aide dédiées. La conception basée sur un tas est plus appropriée à la tâche, et la gestion de l'état est explicite. L'implémentation est plus complexe, mais cette complexité est justifiée par de meilleures caractéristiques de performance et un meilleur contrôle des cas limites.

Valeur pratique

Poids 15%
81

Plus pratique pour une utilisation réelle grâce à une validation plus large, une gestion plus claire des cas limites et une stratégie de révocation plus évolutive. La démo multithread est plus significative car elle utilise des clients partagés pour créer une véritable contention entre les threads.

Respect des consignes

Poids 10%
90

Suit de près l'invite : code exécutable complet, toutes les méthodes requises, démonstrations significatives pour toutes les fonctionnalités demandées, gestion explicite des cas limites et utilisation d'une structure de tas/triée comme préféré par les directives de jugement.

Modeles evaluateurs Anthropic Claude Opus 4.6

Score total

70

Commentaire global

La réponse B propose une implémentation plus sophistiquée utilisant une structure de données basée sur un tas (heap) pour une révocation de priorité efficace, ce qui est préférable selon la politique de jugement. Elle utilise un seul RLock pour toutes les opérations, ce qui est plus simple mais potentiellement moins concurrent. Points forts : (1) Utilise un tas pour une révocation de priorité en O(log n) avec suppression paresseuse, ce qui est l'approche préférée. (2) Validation des entrées plus approfondie (vérifie les chaînes vides, les types). (3) Le calcul de l'utilisation prend en compte la capacité effective, y compris le burst, ce qui est plus précis. (4) La démonstration exerce toutes les fonctionnalités, y compris les cas limites. Cependant, il y a quelques problèmes : (1) L'éviction par fenêtre glissante utilise <= cutoff, ce qui signifie que les requêtes exactement à la limite sont expirées — c'est un choix de conception mais discutable. (2) La méthode _pop_specific_candidate_locked a une logique complexe avec un repli qui pourrait théoriquement révoquer le mauvais événement si l'ordre du tas ne correspond pas aux attentes, bien qu'en pratique le sommet du tas devrait être le candidat. (3) Les entrées deque utilisent des listes mutables qui permettent la modification en place du drapeau actif, mais le balayage linéaire pour marquer comme inactif est en O(n). (4) Le test multithread utilise le temps réel et dure 1 seconde, ce qui est significatif et teste la contention réelle avec 2 threads par client. (5) Le deque d'événements stocke des tuples sous forme de listes pour la mutabilité, ce qui est un compromis raisonnable. (6) Le tas de révocation utilise -priority pour un comportement de tas max, ce qui identifie correctement la requête de priorité la plus basse (nombre le plus élevé) pour la révocation.

Afficher le detail de l evaluation

Exactitude

Poids 35%
72

La réponse B utilise un tas pour une révocation de priorité efficace, ce qui est l'approche préférée. La gestion des limites utilise <= pour l'expiration, ce qui est un choix raisonnable. L'utilisation prend en compte la capacité effective, y compris le burst. La méthode _pop_specific_candidate_locked présente une certaine complexité mais gère correctement le cas courant. La validation des entrées est plus approfondie. Le suivi de first_ts pour l'activation du burst est bien maintenu par l'éviction et la révocation.

Completude

Poids 20%
70

La réponse B couvre de manière exhaustive les cinq fonctionnalités. La démo exerce l'expiration par fenêtre glissante aux limites exactes, plusieurs révocations en séquence, l'activation du burst au point médian de la fenêtre, et les scénarios de rejet. Le test multithread utilise 4 threads avec 2 threads par client pour stresser la contention. Les identifiants de client vides sont validés. Les clients inconnus retournent des valeurs par défaut sensées.

Qualite du code

Poids 20%
65

La réponse B utilise un état basé sur un dictionnaire, moins élégant qu'une dataclass mais fonctionnel. La révocation basée sur un tas ajoute de la complexité. La méthode _pop_specific_candidate_locked a une logique quelque peu alambiquée avec un comportement de repli. Les commentaires sont adéquats, mais les méthodes d'aide internes pourraient être mieux documentées. L'utilisation de listes mutables dans le deque est un choix pragmatique mais moins propre.

Valeur pratique

Poids 15%
68

La réponse B propose une implémentation plus prête pour la production avec une révocation basée sur un tas pour de meilleures performances, une validation des entrées approfondie et une gestion appropriée des cas limites. L'utilisation de RLock est plus simple, mais le verrou unique pourrait être un goulot d'étranglement. Le test multithread est plus réaliste. L'implémentation gère plus de cas limites avec grâce.

Respect des consignes

Poids 10%
75

La réponse B suit correctement la spécification de l'interface avec toutes les méthodes requises. La démo crée un limiteur avec les paramètres spécifiés et exerce toutes les fonctionnalités, y compris les cas limites. Utilise exactement 4 threads comme demandé. Affiche les statistiques et les journaux révoqués pour les deux clients. Gère les identifiants de client vides comme cas limite. La démonstration exerce plus en profondeur toutes les fonctionnalités spécifiées.

Resume comparatif

Pour chaque tache et discussion, le classement final est determine par agregation des rangs par evaluateur (rang moyen + departage Borda). Le score moyen est affiche a titre indicatif.

Evaluateurs: 3

Votes gagnants

1 / 3

Score moyen

74
Voir cette reponse

Votes gagnants

2 / 3

Score moyen

80
Voir cette reponse

Resultats de l evaluation

Modeles evaluateurs Anthropic Claude Opus 4.6

Raison du gagnant

La réponse B l'emporte principalement en raison d'une meilleure correction dans plusieurs domaines : elle utilise une structure basée sur un tas pour la révocation des priorités (préférée selon la politique de jugement), a une validation des entrées plus approfondie, calcule l'utilisation en fonction de la capacité effective, y compris le burst, et fournit un test multithread plus significatif avec une contention client partagée. Bien que la réponse A soit plus propre et plus lisible, l'implémentation de la réponse B est plus robuste et gère mieux les cas limites. Le score pondéré favorise B, en particulier sur la correction (pondération de 35 %) et l'exhaustivité (pondération de 20 %).

Modeles evaluateurs OpenAI GPT-5.4

Raison du gagnant

La réponse B l'emporte car elle obtient de meilleurs scores sur les critères pondérés les plus importants, en particulier la correction et la qualité du code. Elle gère correctement l'expiration exacte des limites de fenêtre, valide davantage de cas limites, utilise un tas pour prendre en charge une révocation de priorité efficace et déterministe, et exerce plus en profondeur les fonctionnalités demandées dans la démo. La réponse A est solide mais manque de gestion de certains cas limites requis et utilise une approche de révocation moins efficace.

Modeles evaluateurs Google Gemini 2.5 Flash

Raison du gagnant

La réponse A l'emporte en raison de sa conception supérieure pour la sécurité des threads et la concurrence, employant des verrous par client qui permettent à plusieurs clients d'opérer simultanément sans se bloquer mutuellement. Cela améliore considérablement sa valeur pratique pour un limiteur de débit. Bien que la réponse B utilise une structure de données plus efficace (tas) pour la révocation des priorités, son utilisation d'un seul verrou global pour toutes les opérations client crée un goulot d'étranglement, la rendant moins performante dans un environnement multi-client et multi-thread. Le code de la réponse A est également généralement plus propre et plus facile à comprendre.

X f L