Исходный код 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