Сохраняет ли sorted(by:) исходный порядок элементов, равных по компаратору?
Нет, sorted(by:) не гарантирует стабильность сортировки. Если компаратор считает два элемента эквивалентными, их взаимный порядок после сортировки может сохраниться, но полагаться на это нельзя.
Чтобы получить предсказуемый порядок, добавьте в компаратор явный критерий разрешения равенства, например исходную позицию элемента.
API сортировки отделяет способ сравнения элементов от самого алгоритма сортировки. Это позволяет библиотеке выбирать реализацию с подходящей производительностью и не связывать контракт sorted(by:) с обязательным сохранением порядка эквивалентных элементов.
Стабильность обычно требуется прикладному коду позже: например, при сортировке таблиц сначала по одному, затем по другому критерию. Если стабильность не заявлена контрактом, такой подход становится ненадёжным.
Компаратор может считать несколько элементов равными по выбранному критерию, хотя сами элементы различаются. Например, у записей могут совпадать баллы, но различаться идентификаторы или исходный порядок.
Если после сортировки порядок таких элементов важен, неявная надежда на стабильность приводит к непредсказуемому UI, нестабильным отчётам и тестам, зависящим от конкретной реализации или версии среды.
В sorted(by:) компаратор задаёт отношение порядка. Если для двух элементов компаратор не сообщает, что один должен идти раньше другого, это означает их эквивалентность только с точки зрения сортировки; это не является гарантией сохранения их исходного порядка.
Компаратор должен задавать корректное строгое слабое упорядочивание: быть непротиворечивым, транзитивным и не возвращать противоположные результаты для одной пары элементов. Нарушение этих требований делает результат сортировки недостоверным независимо от стабильности.
Если порядок эквивалентных элементов нужно зафиксировать, добавьте вторичный ключ. Для сохранения исходного порядка можно пронумеровать элементы перед сортировкой:
Здесь исходная позиция используется только как критерий разрешения равенства. Такой вариант создаёт дополнительные обёртки и сравнения, зато делает результат явно детерминированным.
В приложении список пользователей сортируется по рейтингу. У нескольких пользователей рейтинг одинаковый, и бизнес-требование требует сохранять их порядок, заданный сервером.
Вариант с одним сравнением рейтинга короче, но не гарантирует стабильность. Последовательная сортировка сначала по имени, затем по рейтингу тоже небезопасна, если стабильность sorted(by:) не закреплена контрактом.
Выбранное решение — добавить исходную позицию как третичный критерий после рейтинга. Это явно выражает требование, даёт повторяемый результат и не зависит от внутреннего алгоритма сортировки; цена решения — дополнительная память и более сложный компаратор.
==?Нет. Компаратор может сравнивать только одно поле, например балл, тогда как == учитывает все свойства модели. Два объекта с одинаковым баллом могут быть различными значениями и всё равно считаться эквивалентными именно для сортировки.
sorted(by:) по разным ключам?Надёжно — нет. Такой приём работает только при наличии стабильной сортировки: вторая сортировка должна сохранять порядок элементов, равных по её критерию. Поскольку стабильность sorted(by:) не является гарантией, нужно объединить критерии в одном компараторе или использовать алгоритм, для которого стабильность явно заявлена.
Результат нельзя считать корректно отсортированным. Алгоритм получает противоречивые указания: например, один элемент должен идти раньше второго, второй — раньше третьего, а третий — раньше первого. В зависимости от реализации и входных данных это может привести к неожиданному порядку или к ошибке выполнения, поэтому компаратор обязан задавать согласованное отношение порядка.