I have a model called DataModel
, 2 properties with string values. There is a list of this Model as seen below.
struct DataModel: Hashable, Codable {
var value: String
var optionalKey: String?
}
[
{
value: "100",
optionalKey: "value"
},
{
value: "200"
},
{
value: "300",
optionalKey: ""
},
{
value: "400",
optionalKey: "value"
}
]
How can I safely sort the list so that any object where the 'optionalKey' exists, is at the beginning of the list. The desired result is as follows:
[
{
value: "100",
optionalKey: "value"
},
{
value: "400",
optionalKey: "value"
}
{
value: "200"
},
{
value: "300",
optionalKey: "" // <- empty string to be treated as if key does not exist
}
]
CodePudding user response:
To always get the objects where optionalKey
is nil or empty sorted last you can do
let sorted = values.sorted(by: {
let first = ($0.optionalKey ?? "").isEmpty ? 0 : 1
let second = ($1.optionalKey ?? "").isEmpty ? 0 : 1
return first > second
})
CodePudding user response:
If you're deserializing JSON and want an empty string to be nil you can implement your custom init
function:
extension DataModel {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
value = try values.decode(String.self, forKey: .value)
let optionalKey = try? values.decode(String.self, forKey: .optionalKey)
// If `optionalKey` string has no characters assign nil
self.optionalKey = optionalKey?.count == 0 ? nil : optionalKey
}
}
Then sort the list based on the presence of the optionalKey
value:
let list: [DataModel] = decodedData.sorted(by: { lhs, rhs in
// `true` if left side is not `nil` and right side is `nil`
// in other words, non-nil gets priority in the list
return lhs.optionalKey != nil && rhs.optionalKey == nil
})
CodePudding user response:
Te sort closure is quite simple.
You compare two objects: object1
& object2
, where object1
is the first parameter closure and object2
the second one. Here they are DataModel
instances.
You decide between these two that can be ANY object from the array which one should go first by returning true
or false
comparing them.
Under the hood, it can be ANY sorting method (bubble, etc.), it doesn't matter. You just have to compare two elements, and decide which one should go before the other one.
That's the kind of code you should be able to write:
let explicit = models.sorted { object1, object2 in
// They both have optionalKey value
if let optional1 = object1.optionalKey, let optional2 = object2.optionalKey {
if optional1.isEmpty && !optional2.isEmpty { //One if empty -> The other one should go first
return false
} else if !optional1.isEmpty && optional2.isEmpty { //One if empty -> The other one should go first (the other version)
return true
} else { //They are both empties OR both non-empty, we compare then just with value
return object1.value < object2.value
}
} else if let optional1 = object1.optionalKey { //Here, only object2 had nil optionalKey value
if optional1.isEmpty { //optionKey of object1 exists, but is empty
return false
} else { //optionKey of object1 exists, and is not empty
return true
}
} else if let optional2 = object2.optionalKey { //Here, only object2 had nil optionalKey value
if optional2.isEmpty { //optionKey of object1 exists, but is empty
return true
} else { //optionKey of object2 exists, and is not empty
return false
}
} else { //Both are nil
return object1.value < object2.value
}
}
print(explicit)
It's verbose, but it goes case by case, now we can simplify it:
let sorted = models.sorted { object1, object2 in
//Since in your case being nil or empty is the same, let's simplify with giving empty value directly
let optionalValue1 = object1.optionalKey ?? ""
let optionalValue2 = object2.optionalKey ?? ""
if optionalValue1.isEmpty && !optionalValue2.isEmpty {
return false
} else if !optionalValue1.isEmpty && optionalValue2.isEmpty {
return true
} else {
return object1.value < object2.value
}
}
print(sorted)
There could be even more less verbose code, BUT since you aren't able to even do sort it verbosely, I'd keep it as much "simplified". You still need to understand it to debug it/change it in future.
Now, I let that for later but, you might not compare the String values like that, since they are "numbers". For instance, if you have "2"
and "10"
, in a String comparison, "10"
is lower than "2"
, so you'd need to replace object1.value < object2.value
with object1.value.compare(object2.value, options: .numeric) != .orderedDescending
CodePudding user response:
If you want to sort the DataModel object array according to optionalKey status and also value, You should look my code below.
Array element has optionalKey is firstly included in the sorting according to the value then other array elements is included in the sorting.
private func sortDataObjectByValue() -> [DataModel]{
let models = [
DataModel(value: "100", optionalKey: "value"),
DataModel(value: "800", optionalKey: ""),
DataModel(value: "1800", optionalKey: "value"),
DataModel(value: "1200"),
DataModel(value: "900", optionalKey: ""),
DataModel(value: "500", optionalKey: "value")
]
var sortedModels:[DataModel] = []
var modelsWithKey:[DataModel] = []
var modelsWithoutKey:[DataModel] = []
for model in models {
if let key = model.optionalKey, key != "" {
modelsWithKey.append(model)
}
else {
modelsWithoutKey.append(model)
}
}
modelsWithKey = modelsWithKey.sorted(by: { (Int($0.value) ?? 0) < (Int($1.value) ?? 0) } )
sortedModels = modelsWithKey
modelsWithoutKey = modelsWithoutKey.sorted(by: { (Int($0.value) ?? 0) < (Int($1.value) ?? 0) } )
sortedModels = modelsWithoutKey
debugPrint(sortedModels)
return sortedModels
}
0 : DataModel - value : "100" optionalKey : Optional - some : "value"
1 : DataModel - value : "500" optionalKey : Optional - some : "value"
2 : DataModel - value : "1800" optionalKey : Optional - some : "value"
3 : DataModel - value : "800" optionalKey : Optional - some : ""
4 : DataModel - value : "900" optionalKey : Optional - some : ""
5 : DataModel - value : "1200" - optionalKey : nil
Now you have sorted DataModel array according to optionalKey status and value.