"""Идентификаторы событий: неповторяющиеся числа, которые переживут JSON. Уникальность обещана не для красоты: `WatchID` — ключ дедупликации при переигровке дня (спека генератора, раздел 4), и два одинаковых номера склеили бы разные события. Строк дня две породы — трафиковые и торговые, — и рисует их разная случайность, поэтому обещание держится здесь, в одном месте на весь генератор: у приёма одно определение, иначе дисциплина живёт копиями и расходится с ними. """ import numpy as np from numpy.typing import NDArray # Потолок идентификаторов: выше 2^53 числа в JSON (jq, консоль браузера) # округляются при разборе, и id перестаёт быть собой. Настоящая Метрика так # не делает — её id длиннее (мастер-спека, раздел 1.1). LIMIT = 2**53 def unique(rng: np.random.Generator, size: int) -> NDArray[np.uint64]: """Неповторяющиеся id ниже 2^53, разбросанные по диапазону. Не броски наугад, а шаги случайной длины — они не повторяются по построению, — и потом перемешивание, чтобы номер не выдавал порядок строк. Средний шаг — весь диапазон, делённый на число событий, поэтому в среднем ряд занимает его половину. """ step = LIMIT // (size + 1) numbers = np.cumsum(rng.integers(1, step + 1, size, dtype=np.uint64)) return numbers[np.argsort(rng.integers(0, size * size + 1, size), kind="stable")] def unique_apart_from( rng: np.random.Generator, size: int, taken: NDArray[np.uint64] ) -> NDArray[np.uint64]: """То же, но и с занятыми номерами ряд не пересекается. Торговые события берут случайность из своего подпотока — иначе правка торгового поведения сдвинула бы трафик, — а значит, про уже розданные трафиковые номера их ряд ничего не знает. Совпадение двух рядов на диапазоне в 2^53 невероятно, но обещание уникальности держит дедуп, и проверить его дешевле, чем предположить. """ while True: numbers = unique(rng, size) if not np.intersect1d(numbers, taken).size: return numbers