Swift how to sort dict keys by byte value and not alphabetically?
On Apple platforms, Swift strings comparison is a lexicographical comparison of Unicode scalar values, based on the so-called "Unicode Normalization Form D", see
How String Comparison happens in Swift or What does it mean that string and character comparisons in Swift are not locale-sensitive? for details.
On Linux, the sort order is different. That is a known problem
([String] sort order varies on Darwin vs. Linux) and should be fixed in Swift 4.
If you only care about ASCII characters then a possible approach would
be to compare the UTF-8 representation of the strings:
func utf8StringCompare(s1: String, s2: String) -> Bool {
let u1 = s1.utf8
let u2 = s2.utf8
for (x, y) in zip(u1, u2) {
if x < y { return true }
if x > y { return false }
}
return u1.count < u2.count
}
let s = ["AWT", "Ast"].sorted(by: utf8StringCompare)
print(s) // ["AWT", "Ast"]
This gives identical results on Apple platforms and on Linux.
But note that this is not the default sort order for Swift Strings
on Apple platforms. To replicate that on Linux (before it is fixed in Swift 4), the following
algorithm would work:
func unicodeStringCompare(s1: String, s2: String) -> Bool {
let u1 = s1.decomposedStringWithCanonicalMapping.unicodeScalars
let u2 = s2.decomposedStringWithCanonicalMapping.unicodeScalars
for (x, y) in zip(u1, u2) {
if x.value < y.value { return true }
if x.value > y.value { return false }
}
return u1.count < u2.count
}
let someStrings = ["a", "b", "e", "f", "ä", "é"].sorted(by: unicodeStringCompare)
print(someStrings) // ["a", "ä", "b", "e", "é", "f"]
String comparison () returns different results on different platforms?
This is a known open "bug" (or perhaps rather a known limitation):
- SR-530 - [String] sort order varies on Darwin vs. Linux
Quoting Dave Abrahams' comment to the open bug report:
This will mostly be fixed by the new string work, wherein String's
default sort order will be implemented as a lexicographical ordering
of FCC-normalized UTF16 code units.Note that on both platforms we rely on ICU for normalization services,
and normalization differences among different implementations of ICU
are a real possibility, so there will never be a guarantee that two
arbitrary strings sort the same on both platforms.However, for Latin-1 strings such as those in the example, the new
work will fix the problem.
Moreover, from The String Manifest:
Comparing and Hashing Strings
...
Following this scheme everywhere would also allow us to make sorting
behavior consistent across platforms. Currently, we sort String
according to the UCA, except that--only on Apple platforms--pairs of
ASCII characters are ordered by unicode scalar value.
Most likely, the particular example of the OP (covering solely ASCII characters), comparison according to UCA (Unicode Collation Algorithm) is used for Linux platforms, whereas on Apple platforms, the sorting of these single ASCII character String
's (or; String
instances starting with ASCII characters) is according to unicode scalar value.
// ASCII value
print("S".unicodeScalars.first!.value) // 83
print("g".unicodeScalars.first!.value) // 103
// Unicode scalar value
print(String(format: "%04X", "S".unicodeScalars.first!.value)) // 0053
print(String(format: "%04X", "g".unicodeScalars.first!.value)) // 0067
print("S" < "g") // 'true' on Apple platforms (comparison by unicode scalar value),
// 'false' on Linux platforms (comparison according to UCA)
See also the excellent accepted answer to the following Q&A:
- What does it mean that string and character comparisons in Swift are not locale-sensitive?
String sort using CharacterSet order
As a simple implementation of matt's cosorting comment:
// You have `t` twice in your string; I've removed the first one.
let alphabet = "ꜢjiyꜤwbpfmnRrlhḥḫẖzsšqkgtṯdḏ "
// Map characters to their location in the string as integers
let order = Dictionary(uniqueKeysWithValues: zip(alphabet, 0...))
// Make the alphabet backwards as a test string
let string = alphabet.reversed()
// This sorts unknown characters at the end. Or you could throw instead.
let sorted = string.sorted { order[$0] ?? .max < order[$1] ?? .max }
print(sorted)
Sort array with different classes and keys
You can do it like this:
- Create
NSArray *result
with the size of the combined arrays - Concatenate the two arrays into the
result
array by copying the elements of the first array followed by the elements of the second array into theresult
. - Use
sortedArrayUsingComparator:
with a comparator that "understands" both types.
Here is a skeletal implementation:
NSArray *sortedArray = [result sortedArrayUsingComparator: ^(id obj1, id obj2) {
NSString *key1, *key2;
if ([obj1 isKindOfClass:['class has a title' class]]) {
key1 = [obj1 title];
} else { // It's the kind of objects that has a name
key1 = [obj1 name];
}
if ([obj2 isKindOfClass:['class has a title' class]]) {
key2 = [obj2 title];
} else { // It's the kind of objects that has a name
key2 = [obj2 name];
}
// Do the comparison
return [key1 caseInsensitiveCompare:key2];
}];
Custom sorting an array for given order string in Swift 3
If will be easier if you convert myCustomString
into an array of characters. Working with Swift's String
is a bit of a pain the neck since the subscripting API is so clumsy. Here's one way to do it:
Swift 3:
var myArray = ["Dog", "B-1", "C-1", "C-2", "C-3","Home"]
let myCustomString = "DC".characters.map { $0 }
myArray.sort { str1, str2 in
let index1 = str1.isEmpty ? nil : myCustomString.index(of: str1[str1.startIndex])
let index2 = str2.isEmpty ? nil : myCustomString.index(of: str2[str2.startIndex])
switch (index1, index2) {
case (nil, nil):
return str1.compare(str2, options: .numeric) == .orderedAscending
case (nil, _):
return false
case (_, nil):
return true
default:
if index1 != index2 {
return index1! < index2!
} else {
return str1.compare(str2, options: .numeric) == .orderedAscending
}
}
}
print(myArray)
Original answer:
- Split
myArray
into 2 subarrays: one containing all the elements you have an order for; and one for elements that you do not - Sort
subArray1
according to the order you specified - Sort
subarray2
alphabetically - Concatenate
subArray1
andsubArray2
to make the newmyArray
Example:
let myOrder = "DC".characters.map { $0 }
var myArray = ["Dog", "B-1", "C-1", "C-2", "C-3", "Home"]
var subArray1 = [String]()
var subArray2 = [String]()
myArray.forEach {
if $0.isEmpty || !myOrder.contains($0[$0.startIndex]) {
subArray2.append($0)
} else {
subArray1.append($0)
}
}
subArray1.sort { str1, str2 in
let firstChar1 = str1[str1.startIndex]
let firstChar2 = str2[str2.startIndex]
let index1 = myOrder.index(of: firstChar1)!
let index2 = myOrder.index(of: firstChar2)!
if index1 != index2 {
return index1 < index2
} else {
return str1.compare(str2, options: .numeric) == .orderedAscending
}
}
subArray2.sort { $0.compare($1, options: .numeric) == .orderedAscending }
myArray = subArray1 + subArray2
print(myArray)
How to sort NSArray of objects based on one attribute
Use NSSortDescriptor. Just search the documentation on it and there some very simple examples you can copy right over. Here is a simplified example:
NSSortDescriptor *valueDescriptor = [[NSSortDescriptor alloc] initWithKey:@"MyStringVariableName" ascending:YES];
NSArray *descriptors = [NSArray arrayWithObject:valueDescriptor];
NSArray *sortedArray = [myArray sortedArrayUsingDescriptors:descriptors];
And just like that you have a sorted array.
Related Topics
Composing Video and Audio - Video's Audio Is Gone
How to Display an Image by an API Url? Swift
Ios13 Uialertcontroller with Custom View & Preferredstyle as Actionsheet Grayscale All Colors
Value of Type 'Authdataresult' Has No Member 'Uid'
Collection View Layout Bug When Selectitem (Swift 5)
Replace Multiple Words from a String Based on the Values in an Array
Where to Place App Delegate Code in App.Swift File
Need Clarification for Swift Type Properties
Firebase Datadescription Returns Empty Array
Can't Append Value to My Nsarray Variable
Randomize Two Arrays the Same Way Swift
Swift Spritekit 3D Touch and Touches Moved
Swift Deleting Table View Cell When Timer Expires
Use Huge Numbers in Apple Swift
Swiftui: Unexpected Animation When Using a Non @State Var
Performing a Completion Handler Before App Launches
Why Reloaddata Is Showing Error for My Tableview Inside Viewcontroller