4

I have an array of strings (of an app's versions) randomly ordered like:

var array = ["2.12.5", "2.12.10", "2.2", "2.11.8"]

The sorted array should be ["2.12.10", "2.12.5", "2.11.8", "2.2"] (ordered by most recent). I am trying to sort it.

//Compare by string
array.sort { //This returns ["2.2", "2.12.5", "2.12.10", "2.11.8"]
    $0 > $1
}

//Compare by int
array.sort { //This returns ["2.12.10", "2.12.5", "2.11.8", "2.2"]
    (Int($0.replacingOccurrences(of: ".", with: "")) ?? 0) > (Int($1.replacingOccurrences(of: ".", with: "")) ?? 0)
}

None of this is working properly. What is the best way to return the correct array?

Rob
  • 415,655
  • 72
  • 787
  • 1,044
John Doe
  • 141
  • 10
  • Possible duplicate of http://stackoverflow.com/questions/24130026/swift-how-to-sort-array-of-custom-objects-by-property-value – Farside Mar 05 '16 at 14:57
  • Not a duplicate, as I am not asking to sort the array by property value – John Doe Mar 05 '16 at 15:36

4 Answers4

7

The easiest option is to use compare with .numeric:

array.sort { $0.compare($1, options: .numeric) == .orderedDescending }

Obviously, if you want it in ascending order, use .orderedAscending.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • The problem with this solution is that it sorts "2.2" < "2.11.8" (Because 2<11). However in software versioning "2.2" > "2.11.8" – John Doe Mar 05 '16 at 15:46
  • 2
    @JohnDoe Not usually. "2.2" normally means major 2, minor 2 which would come before "2.11" - major 2, minor 11. 9 minor versions have passed since "2.2" –  Mar 05 '16 at 15:48
  • @JohnDoe - See point 2 in http://semver.org/. This semantic versioning is what is used by dependency managers like Carthage. There are certainly different standards for version numbering, but the common convention is that 2.2 < 2.11. – Rob Mar 05 '16 at 15:49
  • 1
    My bad, you guys are right. 2.2 comes before 2.11 and this is the easiest way to sort the array. Marked as the correct solution. Thanks! – John Doe Mar 05 '16 at 15:53
  • @Rob What I don't get is how it compares the two version numbers with `.NumericSearch`. Is that option overridden to compare sets of integers in strings? Very handy for that. –  Mar 05 '16 at 16:08
  • It's a generalized comparison routine when it encounters digits in a string. I quote from the documentation, "Numbers within strings are compared using numeric value, that is, `Name2.txt` < `Name7.txt` < `Name25.txt`. Numeric comparison only applies to the numerals in the string, not other characters that would have meaning in a true number such as a negative sign or a decimal point." This makes it very well suited for version numbers. – Rob Mar 05 '16 at 16:13
  • That doesn't fully explain its behavior. For example, it shows "2foo" as coming before "foo1". I'd just like some more transparency as to the algorithm used. –  Mar 05 '16 at 16:24
  • It's doing a standard string comparison, but employs numeric logic if the strings are identical up to those digits. So "2foo" falls before "foo1" simply because the character "2" is less than the character "f". But "foo2" will be sorted before "foo11" (because "foo" == "foo" and now the digits are the determining factor), but "foo2" will be sorted after "bar11" because "b" < "f". – Rob Mar 05 '16 at 16:31
3

You have to split the version string into its components, convert them to numbers and compare their numeric values. Try this:

var arrayOfStrings = ["2.12.5", "2.12.10", "2.2", "2.11.8"]

arrayOfStrings.sortInPlace {
    let v0 = ($0 as NSString).componentsSeparatedByString(".")
    let v1 = ($1 as NSString).componentsSeparatedByString(".")

    for i in 0..<min(v0.count, v1.count) {
        if v0[i] != v1[i] {
            return Int(v0[i]) < Int(v1[i])
        }
    }

    return v0.count < v1.count
}

This obviously cannot handle version numbers like 2.1a or 3.14b123

Code Different
  • 90,614
  • 16
  • 144
  • 163
1

Swift 4 version:

var arrayOfStrings = ["2.12.5", "2.12.10", "2.2", "2.11.8"]

arrayOfStrings.sorted { (lhs, rhs) in
    let lhsc = lhs.components(separatedBy: ".")
    let rhsc = rhs.components(separatedBy: ".")
    for i in 0..<min(lhsc.count, rhsc.count) where lhsc[i] != rhsc[i] {
        return (Int(lhsc[i]) ?? 0) < (Int(rhsc[i]) ?? 0)
    }
    return lhsc.count < rhsc.count
}
Michael Long
  • 1,046
  • 8
  • 15
0

Swift 4.2:

var arrayOfStrings = ["1.2.1", "1.0.6", "1.1.10"]
arrayOfStrings.sort { (a, b) -> Bool in
    a.compare(b, options: String.CompareOptions.numeric, range: nil, locale: nil) == .orderedAscending
}
print(arrayOfStrings) // ["1.0.6", "1.1.10", "1.2.1"]
Nagendra Rao
  • 7,016
  • 5
  • 54
  • 92