Исходный код EJIO.utils.random.pool_generator

## /utils/random/pool_generator.py

import random
from concurrent.futures import ThreadPoolExecutor

__all__ = [
    'generate_weighted_pool',
]


def _generate_batch[T](items: list[T], weights: list[float], batch_size: int) -> list[T]:
    """
    Внутренний изолированный воркер: генерирует один батч выборки на уровне C-кода.

    :param items: Список объектов выборки.
    :param weights: Список весов.
    :param batch_size: Размер выборки.
    :return: Список из объектов, полученный в результате выборки.
    """
    return random.choices(items, weights=weights, k=batch_size)


[документация] def generate_weighted_pool[T](weighted_items: dict[T, float], total_pulls: int, /) -> list[T]: """ Генерирует список случайных объектов на основе переданного словаря весов (вероятностей). Автоматически масштабируется и использует многопоточность для ускорения генерации больших пулов. Пример: generate_weighted_pool({ItemA: 0.7, ItemB: 0.3}, total_pulls=100000) :param weighted_items: Словарь вида {объект: float_вероятность}. Веса могут не суммироваться в 1.0. :param total_pulls: Общее количество извлечений (размер результирующего списка). :return: Список случайных объектов. :raises AttributeError: Если параметры заданы неверно. """ if not weighted_items: raise AttributeError("weighted_items must not be empty") if total_pulls <= 0: raise AttributeError("total_pulls must be greater than 0") # Разделяем ключи и веса на два синхронных массива для random.choices items = list(weighted_items.keys()) weights = list(weighted_items.values()) # Критический порог оптимизации: если пул маленький, потоки создавать не имеет смысла if total_pulls < 50_000: return random.choices(items, weights=weights, k=total_pulls) # Оптимальный размер батча на один поток для минимизации накладных расходов GIL batch_size = 25_000 num_workers = min(8, (total_pulls // batch_size) or 1) # Рассчитываем точные размеры кусков для каждого потока quotient, remainder = divmod(total_pulls, num_workers) batch_sizes = [quotient] * num_workers if remainder: batch_sizes[-1] += remainder results: list[T] = [] with ThreadPoolExecutor(max_workers=num_workers) as executor: futures = [ executor.submit(_generate_batch, items, weights, size) for size in batch_sizes ] for future in futures: try: results.extend(future.result()) except Exception: raise return results