package store import "context" // pickBest выбирает победителя среди кандидатов на одни координаты. // // **Победитель — функция МНОЖЕСТВА кандидатов, а не порядка их поступления.** // Попарная свёртка этого не даёт: полнота (или покрытие) — частичный порядок, // тай-брейк — тотальный, и вместе они образуют нетранзитивное отношение победы, // то есть цикл. При цикле повторная свёртка одной и той же доставки меняет // содержимое витрины, и она перестаёт быть свёрткой журнала. Проверено дважды: // сперва на точках, где нетранзитивность нашлась перебором троек, потом на // сущностях, где ту же ошибку повторили молча. // // Отсюда и общий помощник вместо второй рукописной копии: механизм один, // отношения разные. Правка одного экземпляра при живом втором уже случилась: // тай-брейк точек сменён с байтового порядка на «побеждает пришедшая», а // тай-брейк сущностей остался позицией журнала — потому что у сущности есть // колонка провенанса, а у точки нет. Механизм при этом не разъехался ровно // благодаря общему помощнику; критерий выбора между двумя правилами записан в // `architecture.md`, раздел «Разрешение столкновений». // // dominates(a, b) обязан быть СТРОГИМ превосходством: a не хуже b и b не не // хуже a. Иначе взаимно покрывающие друг друга кандидаты выбьют друг друга, и // множество непревзойдённых окажется пустым. // // less обязан быть ТОТАЛЬНЫМ строгим порядком: при неединственном минимуме // победителем оказывается просто первый в срезе, то есть порядок элементов на // проводе, а он у HAE нестабилен. // // Отбор непревзойдённых КВАДРАТИЧЕН по числу кандидатов, и это названо вслух, // потому что число кандидатов приходит из чужого тела. Отсюда `ctx`: цикл, чья // стоимость определяется размером входа, обязан видеть отмену. Без него тело с // двадцатью тысячами версий одного ключа занимало бы единственного воркера // свёртки дольше, чем длится его же дедлайн, — то есть дедлайн, заведённый // ровно против такого случая, не значил бы ничего. Замерено: n=4000 — 3.9 с, // n=8000 — вчетверо больше. // // Вызывающий обязан сокращать множество до входа сюда: совпавших кандидатов // схлопывать, а число различных — ограничивать. Помощник этого не делает // сам — что считать «тем же» кандидатом, знает только он. // // Возвращает индекс победителя и индексы непревзойдённых — вторые нужны тем, // кто считает несравнимость среди них. Пустой срез кандидатов — нарушенный // инвариант вызывающего: оба сегодняшних вызова собирают множество из карты и // пустого дать не могут. func pickBest[T any](ctx context.Context, cands []T, dominates func(a, b T) bool, less func(a, b T) bool) (winner int, maximal []int, err error) { if len(cands) == 0 { return 0, nil, errNoCandidates } maximal = make([]int, 0, len(cands)) for i := range cands { if err := ctx.Err(); err != nil { return 0, nil, err } beaten := false for j := range cands { if i == j { continue } if dominates(cands[j], cands[i]) { beaten = true break } } if !beaten { maximal = append(maximal, i) } } winner = maximal[0] for _, i := range maximal[1:] { if less(cands[i], cands[winner]) { winner = i } } return winner, maximal, nil }