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 en Python un limiteur de débit thread-safe qui prend en charge les fonctionnalités suivantes : 1. **Limitation de débit par fenêtre glissante** : Le limiteur doit utiliser un algorithme de fenêtre glissante (et non des fenêtres fixes) pour suivre le nombre de requêtes. Étant donné un maximum de `max_requests` autorisées sur une période `window_seconds`, il doit déterminer avec précision si une nouvelle requête est autorisée à un instant donné. 2. **Plusieurs niveaux (tiers)** : Le limiteur...

Afficher plus

Concevez et implémentez en Python un limiteur de débit thread-safe qui prend en charge les fonctionnalités suivantes : 1. **Limitation de débit par fenêtre glissante** : Le limiteur doit utiliser un algorithme de fenêtre glissante (et non des fenêtres fixes) pour suivre le nombre de requêtes. Étant donné un maximum de `max_requests` autorisées sur une période `window_seconds`, il doit déterminer avec précision si une nouvelle requête est autorisée à un instant donné. 2. **Plusieurs niveaux (tiers)** : Le limiteur de débit doit prendre en charge plusieurs niveaux nommés (par exemple, "free", "standard", "premium"), chacun avec sa propre configuration `max_requests` et `window_seconds`. Les clients se voient attribuer un niveau lors de leur enregistrement. 3. **File de priorité pour requêtes différées** : Lorsqu'une requête est limitée par le débit, au lieu de la rejeter simplement, le limiteur doit l'enfiler dans une file de priorité par niveau. Chaque requête possède une priorité entière (nombre inférieur = priorité plus élevée). Le limiteur doit fournir une méthode qui, lorsqu'une capacité se libère, désenfile et traite la requête en attente de plus haute priorité pour un client donné. 4. **Sécurité thread (Thread Safety)** : Toutes les opérations (allow_request, enqueue, dequeue, register_client) doivent être sûres à appeler depuis plusieurs threads simultanément. 5. **Nettoyage** : Fournir une méthode pour supprimer les données de suivi expirées pour les clients qui n'ont pas fait de requêtes dans les `cleanup_threshold_seconds` (configurable). Votre implémentation doit inclure : - Une classe `RateLimiter` avec l'interface décrite. - Une dataclass ou un named tuple `Request` contenant au minimum : `client_id`, `timestamp`, `priority`, et `payload`. - Une gestion appropriée des cas limites : enregistrement de clients en double, requêtes pour des clients non enregistrés, files de priorité vides, modifications concurrentes, et problèmes de précision d'horloge. Écrivez également un script de démonstration (dans le bloc `if __name__ == "__main__"`) qui : - Crée un limiteur de débit avec au moins deux niveaux. - Enregistre plusieurs clients. - Simule une rafale de requêtes provenant de plusieurs threads, montrant certaines autorisées et d'autres enfilées. - Montre des requêtes différées traitées lorsque la capacité se libère. - Affiche des sorties claires montrant la séquence des événements. Expliquez vos choix de conception dans des commentaires, en particulier concernant votre implémentation de la fenêtre glissante, votre choix de primitives de synchronisation, et tout compromis que vous avez fait entre précision et performance.

Informations complementaires

Cette tâche évalue la capacité à concevoir une structure de données concurrente non triviale combinant plusieurs concepts algorithmiques : compteurs de fenêtre glissante, files de priorité et gestion d'état thread-safe. Une bonne solution démontrera un design d'API clair, une synchronisation correcte sans verrous excessifs, une gestion appropriée des cas limites, et une documentation claire des compromis.

Politique d evaluation

Une réponse de haute qualité doit être évaluée selon les dimensions suivantes : Exactitude : L'algorithme de fenêtre glissante doit réellement glisser (ne pas utiliser des compartiments temporels fixes qui se réinitialisent). La file de priorité doit ordonner correctement selon la priorité. La sécurité thread doit être réelle, pas seulement revendiquée — cherchez une utilisation correcte de verrous ou d'autres mécanismes de synchronisation autour de l'état mutable partagé. Exhaustivité : Les cinq fonctionnalités...

Afficher plus

Une réponse de haute qualité doit être évaluée selon les dimensions suivantes : Exactitude : L'algorithme de fenêtre glissante doit réellement glisser (ne pas utiliser des compartiments temporels fixes qui se réinitialisent). La file de priorité doit ordonner correctement selon la priorité. La sécurité thread doit être réelle, pas seulement revendiquée — cherchez une utilisation correcte de verrous ou d'autres mécanismes de synchronisation autour de l'état mutable partagé. Exhaustivité : Les cinq fonctionnalités listées dans l'énoncé doivent être implémentées. Le script de démonstration doit être exécutable et montrer clairement le limiteur de débit en action avec plusieurs threads. Traitement des cas limites : Le code doit gérer gracieusement les clients non enregistrés, les enregistrements en double, les files vides et les modèles d'accès concurrent. Il ne doit pas planter ni produire de résultats incorrects dans ces conditions. Qualité du design : Recherchez une séparation claire des responsabilités, une structure de classe/méthode sensée, et l'utilisation appropriée de la bibliothèque standard Python (threading, heapq, dataclasses, time). Les solutions excessivement complexes qui n'apportent pas de valeur doivent être pénalisées ; l'élégante simplicité doit être récompensée. Documentation : Les commentaires doivent expliquer l'approche de la fenêtre glissante choisie, pourquoi des primitives de synchronisation spécifiques ont été sélectionnées, et les compromis effectués (par ex. mémoire vs précision, granularité des verrous). Des commentaires vagues ou absents réduisent la qualité. Démonstration : Le script de démonstration doit produire une sortie lisible montrant clairement les requêtes autorisées, limitées, enfilées, et plus tard désenfilées. Il doit utiliser plusieurs threads pour exercer réellement la concurrence. Déduisez des points pour : conditions de course, logique incorrecte de fenêtre glissante, fonctionnalités manquantes, absence réelle de threading dans la démo, ou code qui ne s'exécuterait pas tel quel en Python 3.8+.

Reponses

Réponse : ```python import time import threading import collections import heapq import uuid from dataclasses import dataclass, field from typing import Dict, List, Tuple, Any, Optional # --- Configuration --- # Définir les niveaux avec leurs limites de débit et tailles de fenêtre respectives. # Une valeur de window_seconds plus faible signifie une limitation de débit plus stricte. TIER_CONFIG = { "free": {"max_requests": 5, "window_seconds": 60}, "standard": {"max_requests": 50, "window_seconds": 60}, "premium": {"max_requests": 200, "window_seconds": 60}, } # Durée de conservation des données cl...

Afficher la reponse complete

Réponse : ```python import time import threading import collections import heapq import uuid from dataclasses import dataclass, field from typing import Dict, List, Tuple, Any, Optional # --- Configuration --- # Définir les niveaux avec leurs limites de débit et tailles de fenêtre respectives. # Une valeur de window_seconds plus faible signifie une limitation de débit plus stricte. TIER_CONFIG = { "free": {"max_requests": 5, "window_seconds": 60}, "standard": {"max_requests": 50, "window_seconds": 60}, "premium": {"max_requests": 200, "window_seconds": 60}, } # Durée de conservation des données client avant nettoyage en l’absence d’activité. CLEANUP_THRESHOLD_SECONDS = 300 # --- Structures de données --- @dataclass(order=True) class Request: """Représente une requête avec une priorité, pour la file de priorité.""" # Nous utilisons priority comme premier élément pour que heapq effectue le tri dessus. # Un nombre plus faible signifie une priorité plus élevée. priority: int # Utiliser un ID unique pour départager les égalités de priorité et garantir un tri stable. request_id: str = field(default_factory=lambda: str(uuid.uuid4())) client_id: str timestamp: float payload: Any def __lt__(self, other): # Comparaison personnalisée pour heapq : prioriser par priorité, puis par request_id pour la stabilité. if self.priority != other.priority: return self.priority < other.priority return self.request_id < other.request_id class SlidingWindowCounter: """Implémente un compteur à fenêtre glissante pour la limitation de débit.""" def __init__(self, max_requests: int, window_seconds: float): self.max_requests = max_requests self.window_seconds = window_seconds # Utiliser une deque pour stocker les horodatages des requêtes dans la fenêtre. # Les deque offrent des ajouts et suppressions efficaces aux deux extrémités. self.request_timestamps: collections.deque[float] = collections.deque() # Verrou pour un accès thread-safe à request_timestamps. self.lock = threading.Lock() def record_request(self) -> bool: """Enregistre une requête et renvoie True si elle reste dans la limite, False sinon.""" with self.lock: current_time = time.time() # Supprimer les horodatages plus anciens que la fenêtre. while self.request_timestamps and self.request_timestamps[0] <= current_time - self.window_seconds: self.request_timestamps.popleft() # Vérifier si l’ajout d’une nouvelle requête dépasse la limite. if len(self.request_timestamps) < self.max_requests: self.request_timestamps.append(current_time) return True else: return False def get_current_count(self) -> int: """Renvoie le nombre actuel de requêtes dans la fenêtre.""" with self.lock: current_time = time.time() # Nettoyer les anciens horodatages avant de renvoyer le compte. while self.request_timestamps and self.request_timestamps[0] <= current_time - self.window_seconds: self.request_timestamps.popleft() return len(self.request_timestamps) class RateLimiter: """Un limiteur de débit thread-safe avec plusieurs niveaux, fenêtre glissante et files de priorité.""" def __init__(self, tier_config: Dict[str, Dict[str, Any]], cleanup_threshold_seconds: float): self.tier_config = tier_config self.cleanup_threshold_seconds = cleanup_threshold_seconds # Stocke les compteurs à fenêtre glissante pour chaque niveau. self.tier_counters: Dict[str, SlidingWindowCounter] = {} # Stocke la file de priorité des requêtes différées pour chaque niveau. self.tier_queues: Dict[str, List[Request]] = collections.defaultdict(list) # Stocke l’horodatage de la dernière activité pour chaque client en vue du nettoyage. self.client_last_activity: Dict[str, float] = {} # Stocke le niveau attribué à chaque client. self.client_tiers: Dict[str, str] = {} # Verrou principal pour protéger les structures de données partagées comme client_tiers, client_last_activity et tier_queues. # SlidingWindowCounter possède son propre verrou interne. self.global_lock = threading.Lock() # Initialiser les compteurs par niveau. for tier_name, config in tier_config.items(): self.tier_counters[tier_name] = SlidingWindowCounter( max_requests=config["max_requests"], window_seconds=config["window_seconds"] ) # Démarrer un thread d’arrière-plan pour le nettoyage. self.cleanup_thread = threading.Thread(target=self._cleanup_task, daemon=True) self.cleanup_thread.start() def register_client(self, client_id: str, tier: str) -> bool: """Enregistre un client avec un niveau spécifique. Renvoie True en cas de succès, False si le client existe déjà ou si le niveau est invalide.""" if tier not in self.tier_config: print(f"[!] Erreur : le niveau '{tier}' n’existe pas.") return False with self.global_lock: if client_id in self.client_tiers: print(f"[!] Avertissement : le client '{client_id}' est déjà enregistré.") return False self.client_tiers[client_id] = tier self.client_last_activity[client_id] = time.time() print(f"[*] Client '{client_id}' enregistré avec le niveau '{tier}'.") return True def _get_client_tier(self, client_id: str) -> Optional[str]: """Récupère en toute sécurité le niveau pour un client_id donné.""" with self.global_lock: return self.client_tiers.get(client_id) def _update_client_activity(self, client_id: str): """Met à jour l’horodatage de dernière activité d’un client.""" with self.global_lock: self.client_last_activity[client_id] = time.time() def allow_request(self, client_id: str) -> bool: """Vérifie si une requête provenant de client_id est autorisée selon la limite de débit de son niveau.""" tier = self._get_client_tier(client_id) if not tier: print(f"[!] Erreur : le client '{client_id}' n’est pas enregistré.") return False counter = self.tier_counters.get(tier) if not counter: # Cela ne devrait pas arriver si tier_config est valide et si l’initialisation est correcte. print(f"[!] Erreur interne : compteur introuvable pour le niveau '{tier}'.") return False if counter.record_request(): self._update_client_activity(client_id) # print(f"[*] Requête AUTORISÉE pour le client '{client_id}' (Niveau : {tier}). Compte actuel : {counter.get_current_count()}") return True else: # print(f"[*] Requête REFUSÉE pour le client '{client_id}' (Niveau : {tier}). Compte actuel : {counter.get_current_count()}") return False def enqueue_request(self, client_id: str, priority: int, payload: Any): """Place une requête limitée par le débit dans la file de priorité appropriée.""" tier = self._get_client_tier(client_id) if not tier: print(f"[!] Erreur : le client '{client_id}' n’est pas enregistré. Impossible de mettre en file.") return request = Request(priority=priority, client_id=client_id, timestamp=time.time(), payload=payload) with self.global_lock: heapq.heappush(self.tier_queues[tier], request) print(f"[*] Requête MISE EN FILE pour le client '{client_id}' (Niveau : {tier}, Priorité : {priority}). Taille de la file : {len(self.tier_queues[tier])}") self._update_client_activity(client_id) def dequeue_and_process(self, client_id: str) -> Optional[Tuple[Request, bool]]: """Tente de retirer de la file et de traiter la requête de plus haute priorité pour le niveau d’un client si de la capacité est disponible.""" tier = self._get_client_tier(client_id) if not tier: print(f"[!] Erreur : le client '{client_id}' n’est pas enregistré. Impossible de retirer de la file.") return None counter = self.tier_counters.get(tier) if not counter: print(f"[!] Erreur interne : compteur introuvable pour le niveau '{tier}'.") return None # Vérifier si de la capacité est disponible AVANT d’essayer de retirer de la file. # Cela évite des verrous inutiles et des opérations sur le tas si aucune capacité n’existe. if counter.get_current_count() < counter.max_requests: with self.global_lock: queue = self.tier_queues[tier] if queue: # Revérifier la capacité à l’intérieur du verrou pour gérer les conditions de concurrence. # Il est possible qu’un autre thread ait juste rempli la capacité. if counter.get_current_count() < counter.max_requests: highest_priority_request = heapq.heappop(queue) # Tenter d’enregistrer à nouveau la requête. Cela devrait réussir si de la capacité est disponible. if counter.record_request(): self._update_client_activity(highest_priority_request.client_id) print(f"[*] Requête RETIRÉE DE LA FILE et TRAITÉE pour le client '{highest_priority_request.client_id}' (Niveau : {tier}, Priorité : {highest_priority_request.priority}). Taille de la file : {len(queue)}") return highest_priority_request, True else: # Il s’agit d’une rare condition de concurrence : la capacité était disponible, mais record_request a échoué. # La remettre dans la file et consigner une erreur. heapq.heappush(queue, highest_priority_request) print(f"[!] Condition de concurrence : échec de l’enregistrement de la requête retirée de la file pour {highest_priority_request.client_id}. Réinsérée.") return highest_priority_request, False else: # La capacité a été prise par un autre thread entre la vérification externe et cette vérification interne. # Remettre la requête dans la file si elle a été retirée. heapq.heappush(queue, highest_priority_request) # print(f"[*] Tentative de retrait de file échouée pour {client_id} : capacité remplie par un autre thread.") return highest_priority_request, False else: # La file est vide. # print(f"[*] Tentative de retrait de file pour {client_id} : la file est vide.") return None, False else: # Aucune capacité disponible. # print(f"[*] Tentative de retrait de file pour {client_id} : aucune capacité disponible.") return None, False def _cleanup_task(self): """Tâche d’arrière-plan pour supprimer les données des clients inactifs.""" while True: time.sleep(self.cleanup_threshold_seconds / 2) # Vérifier périodiquement current_time = time.time() clients_to_remove = [] with self.global_lock: for client_id, last_activity in self.client_last_activity.items(): if current_time - last_activity > self.cleanup_threshold_seconds: clients_to_remove.append(client_id) for client_id in clients_to_remove: tier = self.client_tiers.pop(client_id, None) if tier: self.client_last_activity.pop(client_id, None) # Optionnellement, vider les files de ce client si nécessaire, bien que cela ne soit généralement pas fait. # Par simplicité, nous laissons les requêtes mises en file telles quelles. print(f"[*] Nettoyage du client inactif '{client_id}'.") # --- Script de démonstration --- def simulate_requests(rate_limiter: RateLimiter, client_id: str, num_requests: int, delay: float, priority_offset: int = 0): """Simule une rafale de requêtes provenant d’un seul client.""" print(f"--- Simulation de {num_requests} requêtes du client '{client_id}' ---") for i in range(num_requests): # Simuler des priorités variables pour les requêtes mises en file priority = i % 5 + priority_offset # Nombre plus faible = priorité plus élevée payload = f"data_{client_id}_{i}" if rate_limiter.allow_request(client_id): print(f"[Thread {threading.current_thread().name}] Requête {i+1}/{num_requests} AUTORISÉE pour '{client_id}'. Payload : {payload}") else: print(f"[Thread {threading.current_thread().name}] Requête {i+1}/{num_requests} REFUSÉE pour '{client_id}'. Mise en file avec priorité {priority}.") rate_limiter.enqueue_request(client_id, priority, payload) time.sleep(delay) def process_deferred_requests(rate_limiter: RateLimiter, client_id: str, num_attempts: int): """Essaie périodiquement de traiter les requêtes différées d’un client.""" print(f"--- Tentative de traitement des requêtes différées pour le client '{client_id}' ---") for i in range(num_attempts): time.sleep(1.5) # Attendre un peu afin qu’une capacité puisse potentiellement se libérer print(f"[Thread de traitement] Tentative {i+1}/{num_attempts} de retrait de file pour '{client_id}'...") result = rate_limiter.dequeue_and_process(client_id) if result: request, success = result if success: print(f"[Thread de traitement] Traitement réussi : {request.payload} (Priorité : {request.priority})") else: print(f"[Thread de traitement] Échec du traitement (problème de capacité ?) : {request.payload} (Priorité : {request.priority}). Réinsérée dans la file.") else: print(f"[Thread de traitement] Aucune requête différée traitée pour '{client_id}' lors de cette tentative (file vide ou aucune capacité).") if __name__ == "__main__": print("--- Démonstration du limiteur de débit ---") # Initialiser le limiteur de débit limiter = RateLimiter(tier_config=TIER_CONFIG, cleanup_threshold_seconds=CLEANUP_THRESHOLD_SECONDS) # Enregistrer les clients clients = { "client_A": "free", "client_B": "standard", "client_C": "premium", "client_D": "free", "client_E": "standard", } for cid, tier in clients.items(): limiter.register_client(cid, tier) print("\n--- Simulation de requêtes concurrentes ---") # Simuler une rafale de requêtes depuis plusieurs threads threads = [] # Client A (niveau free) - atteindra probablement la limite de débit t1 = threading.Thread(target=simulate_requests, args=(limiter, "client_A", 15, 0.1, 0), name="Thread-A") threads.append(t1) # Client B (niveau standard) - plus de capacité t2 = threading.Thread(target=simulate_requests, args=(limiter, "client_B", 60, 0.05, 1), name="Thread-B") threads.append(t2) # Client C (niveau premium) - capacité la plus élevée t3 = threading.Thread(target=simulate_requests, args=(limiter, "client_C", 250, 0.01, 2), name="Thread-C") threads.append(t3) # Client D (niveau free) - un autre client atteignant la même limite t4 = threading.Thread(target=simulate_requests, args=(limiter, "client_D", 10, 0.15, 3), name="Thread-D") threads.append(t4) # Démarrer les threads de simulation for t in threads: t.start() # Laisser un peu de temps pour les requêtes initiales et la mise en file time.sleep(2) print("\n--- Tentative de traitement des requêtes différées ---") # Démarrer des threads pour traiter les requêtes différées des clients qui ont pu être limités par le débit # Nous essaierons plusieurs fois pour les clients A et D (niveau free). processor_threads = [] tp1 = threading.Thread(target=process_deferred_requests, args=(limiter, "client_A", 5), name="Processor-A") processor_threads.append(tp1) tp2 = threading.Thread(target=process_deferred_requests, args=(limiter, "client_D", 5), name="Processor-D") processor_threads.append(tp2) # Essayer aussi pour le client B, au cas où tp3 = threading.Thread(target=process_deferred_requests, args=(limiter, "client_B", 3), name="Processor-B") processor_threads.append(tp3) for tp in processor_threads: tp.start() # Attendre que tous les threads de simulation aient terminé for t in threads: t.join() # Attendre que les threads de traitement aient terminé leurs tentatives for tp in processor_threads: tp.join() print("\n--- Démonstration terminée ---") print("Vérification de l’état final (les comptes sont approximatifs en raison du timing) :") for tier_name, counter in limiter.tier_counters.items(): print(f"Niveau '{tier_name}' : requêtes actuelles dans la fenêtre : {counter.get_current_count()} (Max : {counter.max_requests})") for tier_name, queue in limiter.tier_queues.items(): print(f"Niveau '{tier_name}' : requêtes différées dans la file : {len(queue)}") # --- Explication des choix de conception --- # 1. Implémentation de la fenêtre glissante : # - Nous utilisons `collections.deque` pour stocker les horodatages des requêtes dans la fenêtre. # - `deque` fournit une complexité O(1) pour l’ajout de nouveaux horodatages et la suppression des anciens à gauche. # - Lorsque `record_request` est appelé, nous supprimons d’abord tous les horodatages plus anciens que `current_time - window_seconds`. # - Cela garantit que `len(self.request_timestamps)` reflète avec précision le nombre de requêtes dans la fenêtre glissante actuelle. # - Compromis : cette approche est précise, mais peut impliquer plusieurs suppressions dans `record_request` si de nombreuses requêtes arrivent en succession rapide puis s’arrêtent. Cependant, au fil du temps, la taille de la deque reste maîtrisée. # - Thread safety : chaque instance de `SlidingWindowCounter` possède son propre `threading.Lock` pour protéger sa deque `request_timestamps`, garantissant que les appels concurrents à `record_request` ou `get_current_count` pour un même niveau sont sérialisés. # 2. Niveaux multiples : # - La classe `RateLimiter` maintient un dictionnaire `tier_config` qui associe les noms de niveaux à leurs paramètres de limitation de débit (`max_requests`, `window_seconds`). # - Elle maintient aussi des instances séparées de `SlidingWindowCounter` et des files de priorité (`tier_queues`) pour chaque niveau. # - L’attribution du niveau client est stockée dans `client_tiers`. # - Cela permet une configuration flexible et une isolation des limites de débit entre différents types de clients. # 3. File de priorité pour les requêtes différées : # - Pour chaque niveau, une liste Python standard est utilisée comme tas min, géré par le module `heapq`. # - La dataclass `Request` est conçue avec `priority` comme clé de tri principale (valeur plus faible = priorité plus élevée). # - Un `request_id` unique est inclus pour garantir un tri stable (FIFO pour des priorités identiques) et éviter les erreurs de comparaison si les horodatages sont identiques. # - `enqueue_request` utilise `heapq.heappush` pour ajouter des requêtes tout en maintenant la propriété du tas. # - `dequeue_and_process` utilise `heapq.heappop` pour récupérer la requête de plus haute priorité. # - Point crucial : `dequeue_and_process` vérifie d’abord la capacité disponible *avant* d’essayer de retirer un élément du tas. Si de la capacité est disponible, il acquiert alors le verrou global, revérifie la capacité (pour gérer les conditions de concurrence), retire la requête et tente ensuite à nouveau `record_request`. Si `record_request` réussit, la requête est traitée. Si cela échoue (en raison d’une condition de concurrence où la capacité vient juste d’être remplie), la requête est remise dans le tas. # - Compromis : l’utilisation de la liste Python + heapq est efficace pour un nombre modéré de requêtes différées. Pour des volumes extrêmement élevés, on pourrait envisager une implémentation plus spécialisée de file de priorité concurrente, mais cela ajoute une complexité significative. # 4. Thread safety : # - `SlidingWindowCounter` : utilise un `threading.Lock` interne pour sa deque `request_timestamps`. # - `RateLimiter` : # - `global_lock` (`threading.Lock`) : protège l’état client partagé (`client_tiers`, `client_last_activity`) et les files de priorité par niveau (`tier_queues`). Ce verrou est acquis pour des opérations comme `register_client`, `enqueue_request` et à l’intérieur de `dequeue_and_process` lors de l’accès/modification des files. # - `allow_request` : appelle `counter.record_request()`, qui est thread-safe. Il met également à jour l’activité client sous le verrou global. # - `dequeue_and_process` : gère soigneusement les verrous. Il vérifie d’abord la capacité *sans* le verrou global. Si de la capacité *peut* être disponible, il acquiert le verrou global, revérifie la capacité, effectue l’opération sur le tas, puis appelle à nouveau `counter.record_request()`, qui est thread-safe. # - Compromis : l’utilisation de plusieurs verrous (un par compteur + un verrou global) vise à réduire la contention par rapport à un verrou global unique pour toutes les opérations. Cependant, les opérations qui couvrent à la fois l’état client et l’état du niveau (comme le retrait de file) nécessitent un ordre de verrouillage soigneux ou l’acquisition du verrou global. # 5. Nettoyage : # - Un thread d’arrière-plan (`_cleanup_task`) parcourt périodiquement `client_last_activity`. # - Il identifie les clients dont l’horodatage de dernière activité est plus ancien que `cleanup_threshold_seconds`. # - Ces clients sont supprimés de `client_tiers` et `client_last_activity` sous `global_lock`. # - Cela empêche `RateLimiter` d’accumuler indéfiniment une quantité de plus en plus importante d’état pour des clients inactifs. # - Compromis : le nettoyage n’est pas instantané ; il s’exécute périodiquement. La valeur de `cleanup_threshold_seconds` détermine le compromis entre l’utilisation mémoire et la réactivité du nettoyage. # 6. Cas limites pris en charge : # - Enregistrement de client en double : `register_client` renvoie False et consigne un avertissement. # - Clients non enregistrés : `allow_request` et `enqueue_request` vérifient `client_tiers` et renvoient/consignent des erreurs. # - Files de priorité vides : `dequeue_and_process` gère les cas où `self.tier_queues[tier]` est vide. # - Modifications concurrentes : traitées par une utilisation appropriée de `threading.Lock`. # - Précision de l’horloge : `time.time()` est utilisé, ce qui est généralement suffisant. Pour des exigences de très haute précision, des horloges monotones spécifiques à la plateforme pourraient être envisagées, mais `time.time()` est standard pour ce type d’application. # - Conditions de concurrence lors du retrait de file : gérées en revérifiant la capacité et en réenregistrant la requête dans la section critique de `dequeue_and_process`. ```

Resultat

#2

Votes gagnants

1 / 3

Score moyen

61
Modeles evaluateurs Anthropic Claude Opus 4.6

Score total

44

Commentaire global

La réponse A fournit une implémentation complète avec les cinq fonctionnalités, une bonne documentation et un script de démonstration. Cependant, elle présente un défaut de conception critique : le compteur de fenêtre glissante est partagé par niveau plutôt que par client, ce qui signifie que tous les clients du même niveau partagent un seul décompte de requêtes. Cela signifie que si client_A (niveau gratuit) utilise 5 requêtes, client_D (également niveau gratuit) serait bloqué même s'ils devraient avoir leur propre quota. C'est un problème fondamental de correction. La file d'attente prioritaire est également par niveau plutôt que par client, ce qui signifie que dequeue_and_process pour un client spécifique pourrait traiter la requête d'un autre client. La méthode dequeue_and_process a un bug où elle référence `highest_priority_request` en dehors du bloc `if queue:` dans la clause `else`, ce qui provoquerait une erreur UnboundLocalError. Le code utilise également `time.time()` au lieu de `time.monotonic()`, le rendant vulnérable aux changements d'horloge système. Le script de démonstration utilise des configurations de niveau élevées (fenêtres de 60 secondes) qui rendent difficile de voir le fonctionnement réel de la fenêtre glissante dans un temps de démonstration raisonnable. Les commentaires sont nombreux mais quelque peu verbeux.

Afficher le detail de l evaluation

Exactitude

Poids 35%
35

La réponse A présente un défaut critique de correction : le compteur de fenêtre glissante est par niveau, pas par client. Tous les clients partageant un niveau partagent un compteur, donc les requêtes de client_A consomment le quota de client_D. La méthode dequeue_and_process présente une erreur potentielle UnboundLocalError (référence highest_priority_request dans une branche else où elle peut ne pas être définie). Utilise time.time() au lieu de time.monotonic(), vulnérable aux sauts d'horloge. La fenêtre glissante elle-même (deque d'horodatages) est correctement implémentée isolément, mais appliquée à la mauvaise granularité.

Completude

Poids 20%
55

La réponse A implémente les cinq fonctionnalités : fenêtre glissante, plusieurs niveaux, file d'attente prioritaire, sécurité des threads et nettoyage (via un thread d'arrière-plan). Cependant, la limitation de débit par niveau plutôt que par client signifie que la fonctionnalité de fenêtre glissante ne fonctionne pas comme prévu. Le script de démonstration est présent et utilise plusieurs threads. Le nettoyage s'exécute comme un thread d'arrière-plan démon. Tous les composants requis (classe dataclass Request, classe RateLimiter, démo) sont présents.

Qualite du code

Poids 20%
50

La réponse A a une structure raisonnable avec une classe SlidingWindowCounter distincte, mais la décision architecturale de partager les compteurs par niveau est un défaut de conception. La classe dataclass Request définit à la fois order=True et __lt__, ce qui est redondant. Le global_lock est utilisé de manière étendue, réduisant les avantages de la concurrence. Les commentaires sont nombreux mais verbeux. Certaines instructions print commentées restent dans le code. La méthode dequeue_and_process est complexe avec des conditions imbriquées et des bugs potentiels.

Valeur pratique

Poids 15%
35

La limitation de débit par niveau de la réponse A la rend peu pratique pour les cas d'utilisation réels où chaque client devrait avoir des limites de débit indépendantes. La démo utilise des fenêtres de 60 secondes, ce qui rend difficile l'observation du comportement de la fenêtre glissante dans un délai raisonnable. Le thread de nettoyage d'arrière-plan est une bonne touche, mais le fait que la fonctionnalité principale soit incorrecte limite considérablement la valeur pratique.

Respect des consignes

Poids 10%
55

La réponse A suit la plupart des instructions : implémente la classe RateLimiter, la classe dataclass Request, gère les cas limites (enregistrement en double, clients non enregistrés), fournit une démo avec plusieurs threads. Cependant, la limitation de débit par niveau plutôt que par client interprète discutablement mal l'exigence selon laquelle 'les clients se voient attribuer un niveau lors de l'enregistrement' - le niveau définit les limites mais chaque client doit être suivi indépendamment. La démo montre des requêtes autorisées et mises en file d'attente. Les choix de conception sont expliqués dans les commentaires.

Modeles evaluateurs OpenAI GPT-5.2

Score total

46

Commentaire global

Implémente un limiteur à fenêtre glissante fonctionnel utilisant une deque d'horodatages et des files d'attente différées basées sur heapq par niveau, ainsi qu'une démo multi-thread et des commentaires de conception approfondis. Cependant, la conception principale est incorrecte pour la limitation de débit par client : le compteur de fenêtre glissante est par niveau (partagé entre tous les clients d'un niveau), de sorte que le trafic d'un client affecte les autres, ce qui viole la sémantique typique et implicite « les clients sont assignés à un niveau » et les attentes de traitement par client du prompt. Il y a également des problèmes de concurrence/logique : enqueue_request appelle _update_client_activity tout en détenant global_lock, mais _update_client_activity réacquiert global_lock (interblocage). dequeue_and_process a un chemin de bug faisant référence à highest_priority_request lorsque la capacité devient indisponible après la vérification interne (la variable peut être indéfinie). Le nettoyage supprime l'enregistrement du client mais laisse des requêtes mises en file d'attente qui ne peuvent plus être traitées (car la recherche du niveau du client échoue).

Afficher le detail de l evaluation

Exactitude

Poids 35%
30

La logique de fenêtre glissante elle-même est correcte, mais elle est implémentée par niveau au lieu de par client, de sorte que les clients du même niveau se limitent mutuellement. enqueue_request provoque un interblocage (global_lock détenu puis _update_client_activity réacquiert global_lock). dequeue_and_process contient un bug de variable indéfinie dans une branche et le nettoyage peut orpheliner les requêtes mises en file d'attente en supprimant l'enregistrement du client.

Completude

Poids 20%
65

Comprend RateLimiter, la dataclass Request, la configuration multi-niveau, les files d'attente prioritaires par niveau, le thread de nettoyage en arrière-plan et une démo multi-thread. Cependant, les opérations clés sont défectueuses (interblocage) et le nettoyage supprime les clients plutôt que simplement le suivi expiré, laissant les éléments différés incohérents.

Qualite du code

Poids 20%
50

Lisible et abondamment commenté, mais présente de graves problèmes structurels : verrouillage imbriqué provoquant un interblocage, types de retour de dequeue incohérents (None vs (None, False)), et une branche boguée faisant référence à une variable non initialisée. Mélange également dataclass(order=True) avec un __lt__ personnalisé inutilement.

Valeur pratique

Poids 15%
45

En pratique, cela peut se bloquer en raison d'un interblocage, et la limitation à l'échelle du niveau le rend inadapté à la limitation de débit réelle basée sur le client. Le nettoyage peut rendre les files d'attente différées inutilisables pour les clients nettoyés. La démo est complète mais peut ne pas se terminer de manière fiable.

Respect des consignes

Poids 10%
60

Suit la plupart des instructions (fenêtre glissante, niveaux, files d'attente prioritaires, affirmations de sécurité des threads, démo, commentaires), mais viole l'intention clé autour de la limitation basée sur le client et présente une gestion des cas limites qui peut échouer (interblocage, requêtes mises en file d'attente orphelines).

Modeles evaluateurs Google Gemini 2.5 Pro

Score total

92

Commentaire global

La réponse A fournit une implémentation du limiteur de débit concurrent très solide, correcte et pratique. Ses choix de conception sont judicieux, notamment la séparation du `SlidingWindowCounter` dans sa propre classe et la logique efficace pour le traitement des requêtes différées à partir d'une file d'attente de niveau partagé. L'implémentation de la sécurité des threads est robuste, avec une gestion minutieuse des conditions de concurrence dans la méthode de défilement. L'inclusion d'un thread de nettoyage automatique en arrière-plan et d'un script de démonstration complet élève encore sa qualité. Bien que l'utilisation d'un seul verrou global pour les données client et les files d'attente soit un compromis mineur, la solution globale est bien conçue et prête pour la production.

Afficher le detail de l evaluation

Exactitude

Poids 35%
90

L'implémentation est correcte pour toutes les fonctionnalités. La logique de la fenêtre glissante est standard et efficace. La file d'attente prioritaire et la logique de défilement sont particulièrement bien gérées, y compris une vérification robuste des conditions de concurrence. Le système global se comporte comme prévu en concurrence.

Completude

Poids 20%
100

Les cinq fonctionnalités requises sont entièrement implémentées. La solution va plus loin en implémentant le mécanisme de nettoyage comme un thread autonome en arrière-plan, ce qui constitue une solution plus complète et plus robuste qu'une simple méthode manuelle.

Qualite du code

Poids 20%
85

La qualité du code est élevée, avec une excellente séparation des préoccupations (par exemple, la classe `SlidingWindowCounter`). L'API est propre et intuitive. L'utilisation d'un seul verrou global pour les files d'attente est une légère simplification mais est correctement implémentée. Le code est bien commenté et lisible.

Valeur pratique

Poids 15%
90

La solution est très pratique. La conception est évolutive, efficace et pourrait être utilisée dans un environnement de production avec des modifications minimales. Le nettoyage automatisé et la logique de défilement robuste en font un composant fiable.

Respect des consignes

Poids 10%
100

La réponse suit parfaitement toutes les instructions de l'invite. Elle implémente toutes les fonctionnalités requises, fournit une classe de données `Request`, inclut un script de démonstration complet et offre une explication détaillée de ses choix de conception.

import threading import time import heapq from collections import deque from dataclasses import dataclass from typing import Any, Dict, Optional, Tuple import itertools import random # Request dataclass holds minimal required fields @dataclass(order=True) class Request: # ordering will be driven primarily by the priority in heaps priority: int client_id: str timestamp: float payload: Any = None class RateLimiter: """ Thread-safe rate limiter with sliding window per client and per-tier priority queues. Design choices (high-level): - Sliding window implemented per client usin...

Afficher la reponse complete

import threading import time import heapq from collections import deque from dataclasses import dataclass from typing import Any, Dict, Optional, Tuple import itertools import random # Request dataclass holds minimal required fields @dataclass(order=True) class Request: # ordering will be driven primarily by the priority in heaps priority: int client_id: str timestamp: float payload: Any = None class RateLimiter: """ Thread-safe rate limiter with sliding window per client and per-tier priority queues. Design choices (high-level): - Sliding window implemented per client using a deque of request timestamps. This gives exact counts for the sliding window (precision) at the cost of storing one timestamp per request. - Per-tier priority queues implemented with heapq. Each queued Request is pushed as (priority, counter, request) to preserve FIFO among same-priority items. Lower priority number = higher priority. - Concurrency: use a combination of locks: * self.clients_lock protects the client registry structure. * each client has its own lock to protect its timestamp deque and last_seen metadata (reduces contention). * each tier has its own lock to protect its priority queue. Lock ordering is important to avoid deadlocks: when both client lock and tier lock need to be acquired, the implementation always acquires the client lock first, then the tier lock. All methods follow this order. - For dequeue_for_client, we must find the highest-priority waiting request for that client in the per-tier heap. Because heapq does not support efficient arbitrary-item removal, we pop items until we find one for the client, buffering others and pushing them back afterwards. This can be O(n) in the queue size in the worst case — a trade-off favoring simpler data structures and correctness over per-operation worst-case performance. - Time functions use time.monotonic() for durations to avoid system clock jumps affecting rate limiting. Trade-offs: - Precision vs space: deque per client is precise for sliding window but stores each request timestamp. - Performance vs complexity: scanning heaps for dequeue_for_client is simple but can be costly if queues are large. """ def __init__(self, tiers: Dict[str, Dict[str, float]]): """ tiers: mapping tier_name -> { 'max_requests': int, 'window_seconds': float } """ self.tiers = {} self.tiers_lock = threading.Lock() self._counter = itertools.count() # Initialize tier structures for name, conf in tiers.items(): if 'max_requests' not in conf or 'window_seconds' not in conf: raise ValueError(f"Tier {name} missing configuration") self.tiers[name] = { 'max_requests': int(conf['max_requests']), 'window_seconds': float(conf['window_seconds']), 'queue': [], # heap: elements (priority, counter, Request) 'lock': threading.Lock(), } # Clients registry: client_id -> { 'tier': str, 'timestamps': deque, 'lock': Lock, 'last_seen': float } self.clients: Dict[str, Dict[str, Any]] = {} self.clients_lock = threading.Lock() def register_client(self, client_id: str, tier_name: str): """Register a client into a tier. Raises ValueError for duplicate registration or unknown tier. Thread-safe. """ with self.clients_lock: if client_id in self.clients: raise ValueError(f"Client {client_id} already registered") if tier_name not in self.tiers: raise ValueError(f"Unknown tier {tier_name}") self.clients[client_id] = { 'tier': tier_name, 'timestamps': deque(), 'lock': threading.Lock(), 'last_seen': time.monotonic(), } def _remove_old_timestamps(self, client_entry: Dict[str, Any], window_seconds: float, now: float): # remove timestamps older than sliding window timestamps: deque = client_entry['timestamps'] cutoff = now - window_seconds while timestamps and timestamps[0] <= cutoff: timestamps.popleft() def allow_request(self, client_id: str, priority: int = 100, payload: Any = None, timestamp: Optional[float] = None) -> Tuple[bool, Optional[Request]]: """ Attempt to allow a request for client_id. If under the sliding window limit, the request is recorded and allowed (returns (True, None)). If the client is at capacity, the request is enqueued into the client's tier priority queue and (False, request) is returned. Raises KeyError if client is not registered. Thread-safe. """ if timestamp is None: now = time.monotonic() else: # If caller supplied an external timestamp, convert it to monotonic-relative by using monotonic() now # and assuming external timestamp is in same epoch; for demonstration we accept timestamp as-is. now = timestamp # Get client entry with self.clients_lock: client_entry = self.clients.get(client_id) if client_entry is None: raise KeyError(f"Client {client_id} is not registered") tier_name = client_entry['tier'] tier = self.tiers[tier_name] # Acquire client lock first (lock ordering rule), then possibly tier lock. with client_entry['lock']: self._remove_old_timestamps(client_entry, tier['window_seconds'], now) if len(client_entry['timestamps']) < tier['max_requests']: # allow request client_entry['timestamps'].append(now) client_entry['last_seen'] = now return True, None else: # at capacity -> enqueue into tier priority queue req = Request(priority=priority, client_id=client_id, timestamp=now, payload=payload) # Acquire tier lock to push into heap with tier['lock']: count = next(self._counter) heapq.heappush(tier['queue'], (req.priority, count, req)) client_entry['last_seen'] = now return False, req def enqueue_request(self, req: Request): """Directly enqueue a Request into its client's tier queue. Raises if client not registered. Thread-safe. """ with self.clients_lock: client_entry = self.clients.get(req.client_id) if client_entry is None: raise KeyError(f"Client {req.client_id} is not registered") tier_name = client_entry['tier'] tier = self.tiers[tier_name] with tier['lock']: count = next(self._counter) heapq.heappush(tier['queue'], (req.priority, count, req)) def dequeue_for_client(self, client_id: str, timestamp: Optional[float] = None) -> Optional[Request]: """ If capacity is available for client_id, dequeue and return the highest-priority queued Request for the client's tier. The returned Request is considered processed and will be counted toward the window. If no queued request is present for the client or capacity isn't available, return None. Raises KeyError if client not registered. Thread-safe. """ if timestamp is None: now = time.monotonic() else: now = timestamp with self.clients_lock: client_entry = self.clients.get(client_id) if client_entry is None: raise KeyError(f"Client {client_id} is not registered") tier_name = client_entry['tier'] tier = self.tiers[tier_name] # Acquire client lock first, then tier lock (consistent ordering) with client_entry['lock']: self._remove_old_timestamps(client_entry, tier['window_seconds'], now) if len(client_entry['timestamps']) >= tier['max_requests']: # no capacity return None # There is capacity; try to find highest-priority queued request for this client with tier['lock']: queue = tier['queue'] if not queue: return None temp = [] found_req = None try: while queue: priority, count, req = heapq.heappop(queue) if req.client_id == client_id: found_req = req break else: temp.append((priority, count, req)) finally: # push back any popped non-matching requests for item in temp: heapq.heappush(queue, item) if found_req is None: return None # mark the request as processed: append timestamp to client's timestamps client_entry['timestamps'].append(now) client_entry['last_seen'] = now return found_req def cleanup(self, cleanup_threshold_seconds: float): """ Remove tracking data for clients who have not made requests in the last cleanup_threshold_seconds. Does not unregister the client (keeps registration), but removes timestamp history to free memory. If a client has queued deferred requests, we keep their tracking data. Thread-safe. """ now = time.monotonic() cutoff = now - cleanup_threshold_seconds removed = [] # iterate clients safely with self.clients_lock: for client_id, client_entry in list(self.clients.items()): # acquire client lock to inspect safely with client_entry['lock']: # if the client has no activity recently and no queued requests, clear timestamps last_seen = client_entry.get('last_seen', 0) tier_name = client_entry['tier'] tier = self.tiers[tier_name] has_queued = False # check if any queued requests exist for this client in tier queue with tier['lock']: for _, _, req in tier['queue']: if req.client_id == client_id: has_queued = True break if last_seen < cutoff and not has_queued: # clear timestamps to free memory client_entry['timestamps'].clear() removed.append(client_id) return removed # Demonstration script if __name__ == "__main__": # Create a rate limiter with two tiers: free and premium tiers_conf = { 'free': {'max_requests': 3, 'window_seconds': 5.0}, 'premium': {'max_requests': 10, 'window_seconds': 5.0}, } rl = RateLimiter(tiers_conf) # Register clients clients = [ ('alice', 'free'), ('bob', 'free'), ('carol', 'premium'), ] for cid, tier in clients: rl.register_client(cid, tier) print(f"Registered client {cid} as tier {tier}") stop_event = threading.Event() # Worker threads simulate bursts of requests def client_worker(client_id: str, burst: int, sleep_between: float): for i in range(burst): allowed, req = rl.allow_request(client_id, priority=random.randint(1, 10), payload={'i': i}) ts = time.monotonic() if allowed: print(f"[{ts:.3f}] {client_id}: request {i} ALLOWED") else: print(f"[{ts:.3f}] {client_id}: request {i} ENQUEUED (priority={req.priority})") time.sleep(sleep_between) # Background processor that periodically tries to dequeue queued requests for all clients def deferred_processor(): while not stop_event.is_set(): # iterate all clients and try to dequeue one request if capacity exists with rl.clients_lock: client_ids = list(rl.clients.keys()) progressed = False for cid in client_ids: try: req = rl.dequeue_for_client(cid) except KeyError: continue if req is not None: ts = time.monotonic() print(f"[{ts:.3f}] Processed deferred request for {cid} (priority={req.priority}, payload={req.payload})") progressed = True if not progressed: # nothing processed; sleep a bit time.sleep(0.2) # Start threads: bursty clients threads = [] t1 = threading.Thread(target=client_worker, args=('alice', 8, 0.4)) t2 = threading.Thread(target=client_worker, args=('bob', 6, 0.3)) t3 = threading.Thread(target=client_worker, args=('carol', 12, 0.1)) threads.extend([t1, t2, t3]) processor_thread = threading.Thread(target=deferred_processor) processor_thread.daemon = True processor_thread.start() for t in threads: t.start() # Let threads finish for t in threads: t.join() # Allow background processor to handle remaining queued items for some time time.sleep(3.0) stop_event.set() processor_thread.join(timeout=1.0) # Show cleanup removed = rl.cleanup(cleanup_threshold_seconds=1.0) print(f"Cleanup removed timestamp history for clients: {removed}") # Show queued items left in each tier for tier_name, tier in rl.tiers.items(): with tier['lock']: qsize = len(tier['queue']) print(f"Tier '{tier_name}' has {qsize} queued requests left") print("Demo complete")

Resultat

#1 | Gagnant

Votes gagnants

2 / 3

Score moyen

76
Modeles evaluateurs Anthropic Claude Opus 4.6

Score total

73

Commentaire global

La réponse B implémente correctement une fenêtre glissante par client, utilise time.monotonic() pour la robustesse de l'horloge, dispose d'un verrouillage granulaire avec un ordre de verrouillage documenté pour éviter les interblocages, et fournit une démonstration bien conçue qui s'exécute dans un délai raisonnable. La limitation de débit par client est l'interprétation correcte du problème. La méthode dequeue_for_client recherche correctement les requêtes appartenant à un client spécifique dans la file d'attente du niveau, bien que le balayage O(n) soit un compromis documenté. La méthode de nettoyage est implémentée comme un appel explicite plutôt qu'un thread d'arrière-plan, ce qui est une déviation mineure mais remplit toujours l'exigence. La méthode allow_request combine la vérification d'autorisation et la mise en file d'attente en un seul appel, ce qui est légèrement différent des méthodes distinctes du prompt, mais fournit toujours la méthode enqueue_request séparément. Le code utilise un ordre de verrouillage approprié (verrouillage client puis verrouillage de niveau) de manière cohérente. Un problème mineur : la classe Request utilise order=True avec des types mixtes, ce qui pourrait causer des problèmes de comparaison, mais les tuples du tas utilisent (priorité, compteur, requête), ce qui évite la comparaison directe des objets Request. La démo est concise et montre clairement la séquence des événements.

Afficher le detail de l evaluation

Exactitude

Poids 35%
75

La réponse B implémente correctement des fenêtres glissantes par client à l'aide d'une deque d'horodatages par client. Utilise time.monotonic() pour la robustesse contre les changements d'horloge. L'ordre de verrouillage est documenté et suivi de manière cohérente (verrouillage client puis verrouillage de niveau). La méthode dequeue_for_client recherche correctement les requêtes du client spécifique dans la file d'attente du niveau. Le tas utilise des tuples (priorité, compteur, requête) pour assurer un ordre stable. Préoccupation mineure : le balayage O(n) dans dequeue est un compromis documenté. La correction globale est solide.

Completude

Poids 20%
70

La réponse B implémente les cinq fonctionnalités. La fenêtre glissante est par client (correct), plusieurs niveaux sont pris en charge, une file d'attente prioritaire avec heapq est implémentée, la sécurité des threads avec verrouillage granulaire est présente, et le nettoyage est fourni sous forme de méthode explicite. La méthode de nettoyage efface les horodatages plutôt que de supprimer les clients, ce qui est une interprétation raisonnable. Le script de démonstration utilise plusieurs threads et un thread de traitement d'arrière-plan. La classe Request contient tous les champs requis.

Qualite du code

Poids 20%
75

La réponse B présente une séparation propre avec un verrouillage granulaire par client et par niveau. L'ordre de verrouillage est explicitement documenté et suivi. L'utilisation d'itertools.count() pour le départage des éléments dans le tas est élégante. L'aide _remove_old_timestamps est bien structurée. La méthode allow_request combinant la vérification et la mise en file d'attente est pragmatique. Le code est bien organisé avec des responsabilités claires pour les méthodes. La méthode de nettoyage vérifiant les requêtes mises en file d'attente avant de les effacer est réfléchie. Problème mineur : la classe Request avec order=True pourrait causer des problèmes avec des types de charge utile non comparables, mais les tuples du tas évitent la comparaison directe des objets Request.

Valeur pratique

Poids 15%
70

La limitation de débit par client de la réponse B est l'approche correcte et pratique. La démo utilise des fenêtres de 5 secondes qui démontrent clairement le comportement de la fenêtre glissante. Le verrouillage granulaire permet une meilleure concurrence dans les scénarios de production. Le balayage O(n) de dequeue_for_client pourrait être une préoccupation à grande échelle mais est documenté. La méthode de nettoyage est pratique et la démo montre son appel. Dans l'ensemble, cette implémentation pourrait servir de point de départ raisonnable pour un limiteur de débit réel.

Respect des consignes

Poids 10%
70

La réponse B suit bien les instructions : implémente la classe RateLimiter avec l'interface décrite, la classe Request avec les champs requis, gère les cas limites (lève ValueError/KeyError de manière appropriée), fournit une démo multi-thread. La méthode allow_request combine la vérification et la mise en file d'attente, ce qui diffère légèrement des méthodes allow_request et enqueue distinctes du prompt, mais enqueue_request est également fournie séparément. La démo montre clairement le traitement autorisé, mis en file d'attente et différé. Les choix de conception sont documentés dans la docstring de la classe.

Modeles evaluateurs OpenAI GPT-5.2

Score total

82

Commentaire global

Fournit une implémentation claire et exécutable avec une véritable fenêtre glissante par client (deque d'horodatages), des files d'attente prioritaires par niveau avec une rupture de priorité FIFO stable, et une stratégie de verrouillage cohérente (verrouillage des clients plus verrous par client et par niveau avec ordre explicite). allow_request met en file d'attente à la limite et renvoie des résultats structurés, dequeue_for_client vérifie correctement la capacité par client, puis extrait la requête mise en file d'attente de priorité la plus élevée pour ce client spécifique (avec un compromis explicite et documenté de O(n) scan de tas). Utilise time.monotonic pour éviter les problèmes de saut d'horloge. Le nettoyage est implémenté et thread-safe, bien qu'il efface l'historique des horodatages plutôt que de supprimer complètement les clients, ce qui est une interprétation raisonnable mais diverge légèrement de « supprimer les données de suivi expirées ». La démo utilise plusieurs threads et un processeur d'arrière-plan pour montrer le traitement différé avec une sortie lisible.

Afficher le detail de l evaluation

Exactitude

Poids 35%
85

Fenêtre glissante correcte par client utilisant un deque avec éviction par coupure ; le tas par niveau est ordonné par (priorité, compteur). La sécurité des threads est largement saine avec un ordre de verrouillage explicite. dequeue_for_client est fonctionnellement correct bien que le scan soit en O(n). Utilise le temps monotonique pour éviter les problèmes d'horloge.

Completude

Poids 20%
85

Implémente toutes les fonctionnalités demandées : fenêtre glissante, plusieurs niveaux, files d'attente prioritaires par niveau, méthodes thread-safe et méthode de nettoyage. La démo montre des rafales concurrentes, la mise en file d'attente et le traitement ultérieur via un processeur d'arrière-plan. Le comportement de nettoyage est légèrement différent (efface les horodatages plutôt que de supprimer les clients) mais supprime toujours les données de suivi expirées.

Qualite du code

Poids 20%
75

Structure propre, bonne docstring expliquant les compromis, stratégie de verrouillage cohérente et utilisation judicieuse de la bibliothèque standard. Une certaine inefficacité (scan de tas pour le dequeue par client) et un commentaire optionnel sur les horodatages légèrement déroutant, mais dans l'ensemble solide et maintenable.

Valeur pratique

Poids 15%
75

Pratique et utilisable comme implémentation de référence : limitation par client, robuste contre les sauts d'horloge, et démontre la concurrence réelle et le traitement différé. Le nettoyage est utile pour le contrôle de la mémoire bien qu'il ne désenregistre pas les clients.

Respect des consignes

Poids 10%
85

Correspond étroitement à l'interface demandée (RateLimiter, dataclass Request, méthodes requises, cas limites via exceptions/retours None, et sortie de démo claire). Légère déviation : le nettoyage efface le suivi au lieu de supprimer les clients, mais s'aligne toujours sur la suppression des données de suivi expirées.

Modeles evaluateurs Google Gemini 2.5 Pro

Score total

73

Commentaire global

La réponse B démontre une compréhension sophistiquée de la concurrence, employant une stratégie de verrouillage à granularité fine et utilisant correctement `time.monotonic()` pour une meilleure précision. La documentation dans la docstring est excellente. Cependant, la solution est gravement compromise par sa conception pour la dé-mise en file d'attente des requêtes différées. La méthode `dequeue_for_client` nécessite de parcourir toute la file de priorité du niveau pour trouver une requête pour un client spécifique, une opération O(n) très inefficace qui ne peut pas être mise à l'échelle. Cette faiblesse algorithmique mine gravement la valeur pratique de la solution, malgré ses forces dans d'autres domaines.

Afficher le detail de l evaluation

Exactitude

Poids 35%
65

La fenêtre glissante et la sécurité des threads de base (utilisant des verrous granulaires et un ordonnancement explicite) sont correctes et bien implémentées. L'utilisation de `time.monotonic` est un plus. Cependant, la logique principale de `dequeue_for_client` est algorithmiquement inefficace (balayage O(n) d'un tas), ce qui constitue un défaut de correction important dans le contexte d'une structure de données haute performance. Cela rend la conception inadaptée à son objectif.

Completude

Poids 20%
90

Les cinq fonctionnalités requises sont implémentées. La fonctionnalité de nettoyage est fournie sous forme de méthode manuelle, ce qui satisfait l'exigence de l'invite de "fournir une méthode" mais constitue une solution moins complète que la tâche d'arrière-plan automatisée fournie par la réponse A.

Qualite du code

Poids 20%
75

Le code démontre des pratiques de programmation concurrente de haute qualité, telles que le verrouillage à granularité fine et les règles explicites d'ordonnancement des verrous. L'utilisation d'exceptions pour la gestion des erreurs est également une bonne pratique. Cependant, la conception est entachée par l'algorithme de dé-mise en file d'attente inefficace, et la classe principale est quelque peu monolithique, détenant tout l'état dans des dictionnaires imbriqués.

Valeur pratique

Poids 15%
50

La valeur pratique est gravement limitée par l'implémentation inefficace de `dequeue_for_client`. Dans tout scénario réel avec un nombre important de requêtes différées, le balayage linéaire de la file de priorité deviendrait un goulot d'étranglement majeur des performances, rendant l'ensemble du système inutilisable. Le verrouillage sophistiqué ne peut pas surmonter cette faille de conception fondamentale.

Respect des consignes

Poids 10%
100

La réponse suit parfaitement toutes les instructions de l'invite. Elle implémente toutes les fonctionnalités requises, fournit une classe dataclass `Request`, inclut un script de démonstration complet et offre une explication détaillée de ses choix de conception dans la docstring de la classe.

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

61
Voir cette reponse

Votes gagnants

2 / 3

Score moyen

76
Voir cette reponse

Resultats de l evaluation

Modeles evaluateurs Google Gemini 2.5 Pro

Raison du gagnant

La réponse A est la gagnante en raison de sa conception de base supérieure et plus pratique. La partie la plus critique de cette tâche, au-delà de la fenêtre glissante de base, est la gestion des requêtes différées. La réponse A met en œuvre un système efficace où la requête de priorité la plus élevée dans la file d'attente d'un niveau est traitée lorsque la capacité devient disponible, ce qui est une approche standard et évolutive. En revanche, la conception de la réponse B pour la désactivation de la file d'attente est fondamentalement inefficace, nécessitant un balayage linéaire de la file d'attente de priorité. Ce défaut algorithmique rend la solution de la réponse B peu pratique pour une utilisation réelle. Bien que la réponse B dispose d'un mécanisme de verrouillage plus avancé, cela ne compense pas le problème de performance critique de sa logique de base. La solution de la réponse A est plus robuste, mieux structurée et plus efficace dans l'ensemble.

Modeles evaluateurs OpenAI GPT-5.2

Raison du gagnant

La réponse B l'emporte sur la base de la notation pondérée car elle met en œuvre correctement la limitation du débit par fenêtre glissante par client avec une véritable sécurité des threads et un mécanisme de priorité différée fonctionnel, tandis que la réponse A présente des bogues majeurs de correction et de concurrence (compteur partagé à l'échelle du niveau causant une interférence entre clients, un interblocage garanti dans enqueue_request et un chemin de défilement défectueux) qui sapent la fonctionnalité principale malgré sa richesse en fonctionnalités et ses commentaires détaillés.

Modeles evaluateurs Anthropic Claude Opus 4.6

Raison du gagnant

La réponse B l'emporte principalement sur la correction (pondération de 35 %). La réponse A présente un défaut de conception fondamental où le compteur de fenêtre glissante est partagé par niveau plutôt que par client, ce qui signifie que les clients du même niveau interfèrent avec les limites de débit des autres. La réponse B implémente correctement des fenêtres glissantes par client. La réponse B utilise également time.monotonic() pour la robustesse de l'horloge, dispose d'un verrouillage granulaire approprié avec un ordre de verrouillage documenté, et fournit une démonstration qui s'exécute dans un délai raisonnable et montre clairement le fonctionnement du limiteur de débit. Bien que la réponse A contienne des commentaires plus détaillés, sa conception algorithmique principale est incorrecte pour les exigences énoncées.

X f L