Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sorting a String array and ignoring case

Tags:

ios

swift

The code below works for sorting an array of strings if they are all lowercase or all uppercase but I want to ignore case when I sort. How could I do this? The following is an array of a custom class.

resultListArray.sort({ $0.fileName.compare($1.fileName) == NSComparisonResult.OrderedAscending })

like image 590
Gary Dorman Avatar asked Sep 06 '15 00:09

Gary Dorman


People also ask

Does localeCompare ignore case?

localeCompare() enables case-insensitive sorting for an array.

Is sorting case sensitive?

Sorting on the basis of the ASCII values differentiates the uppercase letters from the lowercase letters, and results in a case-sensitive order. Although the results in the Ascending order column might at first appear somewhat unpredictable, they are not.

How do you sort an array of strings?

To sort an array of strings in Java, we can use Arrays. sort() function.


2 Answers

You can use String method localizedCompare()

update: Xcode 11.5 • Swift 5.2

let array = ["def","Ghi","Abc" ]

let sorted1 = array.sorted{$0.compare($1) == .orderedAscending} 
print(sorted1)  // ["Abc", "Ghi", "def"] this is case SENSITIVE!

let sorted2 = array.sorted{$0.localizedCompare($1) == .orderedAscending}
print(sorted2) // ["Abc", "def", "Ghi"]


// you can also use the String compare options parameter to give you more control when comparing your strings
let sorted3 = array.sorted{$0.compare($1, options: .caseInsensitive) == .orderedAscending }
print(sorted3)   // ["Abc", "def", "Ghi"]\n"

// which can be simplifyed using the string method caseInsensitiveCompare
let sorted4 = array.sorted{$0.caseInsensitiveCompare($1) == .orderedAscending}
print(sorted4) // ["Abc", "def", "Ghi"]\n"

// or localizedStandardCompare (case and diacritic insensitive)
// This method should be used whenever file names or other strings are presented in lists and tables where Finder-like sorting is appropriate. The exact sorting behavior of this method is different under different locales and may be changed in future releases. This method uses the current locale.
let array5 = ["Cafe B","Café C","Café A"]
let sorted5 = array5.sorted { $0.localizedStandardCompare($1) == .orderedAscending }
print(sorted5) // "["Café A", "Cafe B", "Café C"]\n"

You can also implement your own custom sort/sorted methods:

extension Collection where Element: StringProtocol {
    public func localizedSorted(_ result: ComparisonResult) -> [Element] {
        sorted { $0.localizedCompare($1) == result }
    }
    public func caseInsensitiveSorted(_ result: ComparisonResult) -> [Element] {
        sorted { $0.caseInsensitiveCompare($1) == result }
    }
    public func localizedCaseInsensitiveSorted(_ result: ComparisonResult) -> [Element] {
        sorted { $0.localizedCaseInsensitiveCompare($1) == result }
    }
    /// This method should be used whenever file names or other strings are presented in lists and tables where Finder-like sorting is appropriate. The exact sorting behavior of this method is different under different locales and may be changed in future releases. This method uses the current locale.
    public func localizedStandardSorted(_ result: ComparisonResult) -> [Element] {
        sorted { $0.localizedStandardCompare($1) == result }
    }
}

extension MutableCollection where Element: StringProtocol, Self: RandomAccessCollection {
    public mutating func localizedSort(_ result: ComparisonResult) {
        sort { $0.localizedCompare($1) == result }
    }
    public mutating func caseInsensitiveSort(_ result: ComparisonResult) {
        sort { $0.caseInsensitiveCompare($1) == result }
    }
    public mutating func localizedCaseInsensitiveSort(_ result: ComparisonResult) {
        sort { $0.localizedCaseInsensitiveCompare($1) == result }
    }
    /// This method should be used whenever file names or other strings are presented in lists and tables where Finder-like sorting is appropriate. The exact sorting behavior of this method is different under different locales and may be changed in future releases. This method uses the current locale.
    public mutating func localizedStandardSort(_ result: ComparisonResult) {
        sort { $0.localizedStandardCompare($1) == result }
    }
}

Usage:

var array = ["def","Ghi","Abc" ]
array.caseInsensitiveSort(.orderedAscending)
array    // ["Abc", "def", "Ghi"]


To sort a custom object by a string property we can pass a predicate to get the string from the element and use a keypath when calling this method:

extension Collection {
    /// This method should be used whenever file names or other strings are presented in lists and tables where Finder-like sorting is appropriate. The exact sorting behavior of this method is different under different locales and may be changed in future releases. This method uses the current locale.
    public func localizedStandardSorted<T: StringProtocol>(by predicate: (Element) -> T, ascending: Bool = true) -> [Element] {
        sorted { predicate($0).localizedStandardCompare(predicate($1)) == (ascending ? .orderedAscending : .orderedDescending) }
    }
}

extension MutableCollection where Self: RandomAccessCollection {
    /// This method should be used whenever file names or other strings are presented in lists and tables where Finder-like sorting is appropriate. The exact sorting behavior of this method is different under different locales and may be changed in future releases. This method uses the current locale.
    public mutating func localizedStandardSort<T: StringProtocol>(by predicate: (Element) -> T, ascending: Bool = true) {
        sort { predicate($0).localizedStandardCompare(predicate($1)) == (ascending ? .orderedAscending : .orderedDescending) }
    }
}

Usage:

struct File {
    let id: Int
    let fileName: String
}

var files: [File] = [.init(id: 2, fileName: "Steve"),
                     .init(id: 5, fileName: "Bruce"),
                     .init(id: 3, fileName: "alan")]

let sorted = files.localizedStandardSorted(by: \.fileName)
print(sorted)  // [File(id: 3, fileName: "alan"), File(id: 5, fileName: "Bruce"), File(id: 2, fileName: "Steve")]

files.localizedStandardSort(by: \.fileName)
print(files)  // [File(id: 3, fileName: "alan"), File(id: 5, fileName: "Bruce"), File(id: 2, fileName: "Steve")]

edit/update:

iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0 or later

You can use the new generic structure KeypathComparator

let sorted = files.sorted(using: KeyPathComparator(\.fileName, comparator: .localizedStandard))
print(sorted)  // [File(id: 3, fileName: "alan"), File(id: 5, fileName: "Bruce"), File(id: 2, fileName: "Steve")]
let reversed = files.sorted(using: KeyPathComparator(\.fileName, comparator: .localizedStandard, order: .reverse))
print(reversed)  // [File(id: 2, fileName: "Steve"), File(id: 5, fileName: "Bruce"), File(id: 3, fileName: "alan")]

files.sort(using: KeyPathComparator(\.fileName, comparator: .localizedStandard))
print(files)  // [File(id: 3, fileName: "alan"), File(id: 5, fileName: "Bruce"), File(id: 2, fileName: "Steve")]

For simple types you need to use self as the KeyPath

var array = ["def","Ghi","Abc"]
let sorted = array.sorted(using: KeyPathComparator(\.self, comparator: .localizedStandard))

Or mutating the original

array.sort(using: KeyPathComparator(\.self, comparator: .localizedStandard))
like image 131
Leo Dabus Avatar answered Oct 17 '22 16:10

Leo Dabus


You can convert the String to lowercase and then compare it:

array.sort{ $0.lowercaseString < $1.lowercaseString }
like image 17
Qbyte Avatar answered Oct 17 '22 15:10

Qbyte