Мне нужно отсортировать массив некоторых пользовательских структурных объектов. Моя структура содержит значение Int "user_id", которое я должен использовать для сортировки массива. Пока ничего особенно интересного.
И тогда я должен заставить один элемент (на основе его user_id) оставаться в верхней части моего списка, т.е. как первый элемент моего отсортированного массива.
Я пришел с текущим кодом:
myArray.sorted(by: { $0.user_id == 40 ? true : $0.user_id < $1.user_id })
но иногда (не всегда) кажется, что массив полностью игнорирует первое сравнение и просто сортирует элементы на основе их user_id. Так, например, если в моем списке есть элементы 37, 40, 41, он будет отсортирован как [40, 37, 41] или [37, 40, 41], и я не могу найти, почему и как.
Для контекста: я использую этот список в динамической переменной (я забыл, как они называются) в представлении SwiftUI, которое использует ForEach(mySortedArray) для создания VStack из отсортированных пользовательских строк. Таким образом, в основном код SwiftUI выглядит так (BoardMember — моя пользовательская структура):
@State private var boardPerms: [BoardMember] = []
func fetchMembers() {
boardPerms = ...
}
var displayedMembers: [BoardMember] {
boardPerms.sorted(by: { $0.user_id == 40 ? true : $0.user_id < $1.user_id })
}
var body: some View {
VStack {
ForEach(displayedMembers) { member in
...
}
.onAppear(perform: fetchMembers)
}
и я могу заверить, что проблема в переменной displayedMembers
, потому что я также пытался отладить ее с помощью следующего кода внутри своего тела: Text((displayedMembers.map{$0.user_id.description}).joined(separator: " "))
Есть идеи, что не так с моим сравнением? Спасибо за помощь!
Предикат Array.sorted(по:) требует, чтобы вы
return true if its first argument should be ordered before its second argument; otherwise, false.
Порядок аргументов, передаваемых предикату, может быть указан в любом порядке ((a, b)
или(b, a)
) в зависимости от того, как список был отсортирован до сих пор, но предикат должен дает согласованный результат в любом порядке, иначе вы получите бессмысленные результаты. .
В вашем предикате вы проверяете идентификатор пользователя элемента первый, чтобы определить его приоритет, но нет — второй; т. е. вы обрабатываете (a, b)
, но нет(b, a)
. Если вы обновите свой предикат до
myArray.sorted(by: {
// The order of the checking here matters. Specifically, the predicate
// requires a `<` relationship, not a `<=` relationship. If both $0 and $1
// have the same `user_id`, we need to return `false`. Checking $1.user_id
// first hits two birds with one stone.
if $1.user_id == 40 {
return false
} else if $0.user_id == 40 {
return true
}
return $0.user_id < $1.user_id
})
тогда вы получите стабильные результаты.
В качестве альтернативы, если элемент известен заранее, вы можете избежать этого, извлекая его из списка и сортируя оставшиеся результаты.
Разве предикат не должен возвращать false
для двух элементов равный, например. если оба идентификатора пользователя равны 40?
@MartinR Это правда, хотя звучит так, будто user_id
— уникальное значение. Это также зависит от того, имеет ли значение стабильный порядок, поскольку, если у вас есть n
копии с одинаковым значением, сортировка будет поднимать их все до начала списка, просто в нестабильном порядке. Однако я обновлю ответ, чтобы включить это.
Будет проще, если сначала протестировать if $1.user_id == 40
:)
Предикат должен быть «строгим слабым порядком элементов» и, в частности, иррефлексивным. Я понятия не имею, что может случиться, если это не будет удовлетворено.
@MartinR О, да. Спасибо, обновил.
@LeoDabus Спасибо, исправил опечатку. Вот что я получаю за кодирование в браузере ??♂️
О, я совершенно не подумал о сортировке (б, а), большое спасибо! И да, я подумаю об отделении 1-го элемента от остального списка, хорошая идея.