Swift How to Sort Dict Keys by Byte Value and Not Alphabetically

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 the result.
  • 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 and subArray2 to make the new myArray

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



Leave a reply



Submit