Sorting Array in Swift3 - arrays

In my code, I have a struct like the following:
struct Object {
var name: String
var count: Int
I am now creating an array of 10 Objects with random names and random counts.
Is there an easy way to
a) sort them alphabetically
b) sort them numerically in ascending order
Basically, there will be an array like so:
[Object1, Object2, Object3].
Every Object has a name and count attribute, and I want the objects in that list be sorted via these two attributes.
Solution in Swift2 (using this solution: StackOverflow):
Object.sort{
if $0.name != $1.name {
return $0.name < $1.name
}
else {
//suits are the same
return $0.count < $1.count
}
}
However, this has been renamed to sorted(by: ) in Swift3, and I don't quit get how to do that.

If you want to sort alphabetically and then numerically, you can:
var array = ["A2", "B7", "A4", "C3", "A1", "A10"]
array.sort { $0.compare($1, options: .numeric) == .orderedAscending }
That produces:
["A1", "A2", "A4", "A10", "B7", "C3"]
I added A10 to your array, because without it, a simple alphabetic sort would have been sufficient. But I'm assuming you wanted A10 after A4, in which case the numeric comparison will do the job for you.
You changed the example to be a struct with two properties. In that case, you can do something like:
struct Foo {
var name: String
var count: Int
}
var array = [
Foo(name:"A", count: 2),
Foo(name:"B", count: 7),
Foo(name:"A", count: 7),
Foo(name:"C", count: 3),
Foo(name:"A", count: 1),
Foo(name:"A", count: 10)
]
array.sort { (object1, object2) -> Bool in
if object1.name == object2.name {
return object1.count < object2.count
} else {
return object1.name < object2.name
}
}
Or, more concisely:
array.sort { $0.name == $1.name ? $0.count < $1.count : $0.name < $1.name }
Or
array.sort { ($0.name, $0.count) < ($1.name, $1.count) }
Note, rather than putting this logic in the closure, I'd actually make Foo conform to Comparable:
struct Foo {
var name: String
var count: Int
}
extension Foo: Equatable {
static func ==(lhs: Foo, rhs: Foo) -> Bool {
return (lhs.name, lhs.count) == (rhs.name, rhs.count)
}
}
extension Foo: Comparable {
static func <(lhs: Foo, rhs: Foo) -> Bool {
return (lhs.name, lhs.count) < (rhs.name, rhs.count)
}
}
This keeps the comparison logic nicely encapsulated within the Foo type, where it belongs.
Then you can just do the following to sort in place:
var array = ...
array.sort()
Or, alternatively, you can return a new array if you don't want to sort the original one in place:
let array = ...
let sortedArray = array.sorted()

Narusan, maybe this will help you.
Let's say you have an array with your struct objects called objArray, then you can order it by the code bellow:
var objArray = [Object]()
objArray.append(Object(name:"Steve", count:0))
objArray.append(Object(name:"Alex", count:1))
objNameSorted = objArray.sorted (by: {$0.name < $1.name})
objNCountSorted = objArray.sorted (by: {$0.count < $1.count})

You can still use shorthand for sorted:
objNameSorted = objArray.sorted { $0 < $1 }
While less readable, it more closely mimics the sort syntax.

Related

How to compare elements of two arrays

I want to compare the elements of two arrays and check if they are equal.
I already tried various solutions but nothing really works.
I tried the solution from
How to compare two array of objects?
This is my object:
struct AccountBalance: Decodable {
let balance: Double
let currency: String
init(balance: Double, currency: String ) {
self.currency = currency
self.balance = balance
}
enum CodingKeys: String, CodingKey {
case currency = "Currency"
case balance = "Balance"
}
}
This is the code from the link I tried:
let result = zip(accountBalance, getsSaved).enumerate().filter() {
$1.0 == $1.1
}.map{$0.0}
But I get this error:
Closure tuple parameter '(offset: Int, element: (AccountBalance, AccountBalance))' does not support destructuring with implicit parameters
Array provides a function elementsEqual which is able to compare two arrays without explicitly conforming to Equatable:
let result = accountBalance.elementsEqual(getsSaved) {
$0.balance == $1.balance && $0.currency == $1.currency
}
Edit:
If you want to have the equality result regardless of the order of objects in the array then you can just add sort with each of the arrays.
let result = accountBalance.sorted { $0.balance < $1.balance }.elementsEqual(getsSaved.sorted { $0.balance < $1.balance }) {
$0.balance == $1.balance && $0.currency == $1.currency
}
I am guessing two arrays should be considered equal when they contain the same elements, regardless of ordering.
First, implement Equatable and Hashable.
I am using hashValue as an id so I can sort the arrays first.
Here is what your AccountBalance class should look like:
struct AccountBalance: Decodable, Equatable, Hashable {
// Important parts!
var hashValue: Int{
return balance.hashValue ^ currency.hashValue &* 1677619
}
static func == (lhs: AccountBalance, rhs: AccountBalance) -> Bool{
return lhs.balance == rhs.balance && lhs.currency == rhs.currency
}
}
Then create an algorithm that sorts the ararys and then check each elements by one by by if the contents are the same.
Here is the function that take use of Equatable and Hashable.
func isEqual(arr1: [AccountBalance], arr2: [AccountBalance]) -> Bool{
if arr1.count != arr1.count{
return false
}
let a = arr1.sorted(){
$0.hashValue > $1.hashValue
}
let b = arr2.sorted(){
$0.hashValue > $1.hashValue
}
let result = zip(a, b).enumerated().filter() {
$1.0 == $1.1
}.count
if result == a.count{
return true
}
return false
}
I will suggest you implement the accepted answer of the link you provided because it controls that both arrays are the same size and it orders them.
But if you want that your code works I solved like this:
In order to have control of the comparison your struct should implement the Equatable protocol and overload the operator ==
extension AccountBalance : Equatable {}
func ==(lhs: AccountBalance, rhs: AccountBalance) -> Bool {
return lhs.balance == rhs.balance && lhs.currency == rhs.currency
}
Then compare both arrays and check if contains false, if it does, one or more items in the array aren't the same.
let result = !zip(accountBalance, getsSaved).enumerated().map() {
$1.0 == $1.1
}.contains(false)
Hope it helps you
I'm not sure about what the rest of your code does, but making the parameters explicit does make XCode happy:
let result = zip(accountBalance, getsSaved).enumerated().filter() { (arg) -> Bool in
let (_, (balance1, balance2)) = arg
return balance1.balance == balance2.balance
}.map{ $0.0 }`
Tried (_, (balance1, balance2)) -> Bool in directly, but it wouldn't let me either
Sample with several arrays:
import Foundation
let arr1: [String?] = ["word", nil, "word3", "word4"]
let arr2: [Double?] = [1.01, 1.02, nil, 1.04]
let arr3: [Int?] = [nil, 2, 3, 4]
var tuple1: [(title: String, number: String, coord: String)] = []
let countArray = arr1.count
for element in 0..<countArray {
tuple1.append((arr1[element].map
{String($0)} ?? "", arr2[element].map
{String($0)} ?? "", arr3[element].map
{String($0)} ?? ""))
}
print(tuple1)
result of printing:
[(title: "word1", number: "1.01", coord: ""), (title: "", number: "1.02", coord: "2"), (title: "word3", number: "", coord: "3"), (title: "word4", number: "1.04", coord: "4")]

Finding indices of max value in swift array

I have 2 arrays. One for players and one for scores. e.g.
var players = ["Bill", "Bob", "Sam", "Dave"]
var scores = [10,15,12,15]
I can find the index of the (first) max score (and the winner's name) by using:
let highScore = scores.max()
let winningPlayerIndex = scores.index(of: highScore!)
let winningPlayer = players[winningPlayerIndex!]
This works fine if there is only one player with the highest score but how would I return multiple indices (i.e. 1 and 3 in this example) for all values that are equal to the max value? I need the indices to then map back to the players array to pull out the names of all the players with the highest score. Or is there a better way to do all of this?
The accepted answer doesn't generalize to comparing computed values on the elements. The simplest and most efficient way to get the min/max value and index is to enumerate the list and work with the tuples (offset, element) instead:
struct Player {
let name: String
let stats: [Double]
}
let found = players.enumerated().max(by: { (a, b) in
battingAvg(a.element.stats) < battingAvg(b.element.stats)
})
print(found.element.name, found.offset) // "Joe", 42
In general you shouldn't rely on comparing floating point values by equality and even where you can, if the computation is expensive you don't want to repeat it to find the item in the list.
What you need is to use custom class or structure and make array of it then find max score and after that filter your array with max score.
struct Player {
let name: String
let score: Int
}
Now create array of this Player structure
var players = [Player(name: "Bill", score: 10), Player(name: "Bob", score: 15), Player(name: "Sam", score: 12), Player(name: "Dave", score: 15)]
let maxScore = players.max(by: { $0.0.score < $0.1.score })?.score ?? 0
To get the array of player with max core use filter on array like this.
let allPlayerWithMaxScore = players.filter { $0.score == maxScore }
To get the array of index for player having high score use filter on array like this.
let indexForPlayerWithMaxScore = players.indices.filter { players[$0].score == maxScore }
print(indexForPlayerWithMaxScore) //[1, 3]
To answer just the question in the title -- find the index of the max value in a (single) array:
extension Array where Element: Comparable {
var indexOfMax: Index? {
guard var maxValue = self.first else { return nil }
var maxIndex = 0
for (index, value) in self.enumerated() {
if value > maxValue {
maxValue = value
maxIndex = index
}
}
return maxIndex
}
}
The extension returns nil if the array is empty. Else, it starts by assuming the first value is the max, iterates over all values, updates the index and value to any larger values found, and finally returns the result.
If you have 2 arrays and need to find max score from first one in order to pull the name from second one, then I would recommend you to convert both arrays into one using zip high order func and retrieve the max value from there.
So having your data it will look like this:
let players = ["Bill", "Bob", "Sam", "Dave"]
let scores = [10,15,12,15]
let data = zip(players, scores)
// max score
let maxResult = data.max(by: ({ $0.1 < $1.1 }))?.1 ?? 0
// outputs 15
// leaders
let leaders = data.filter { $0.1 >= maxResult }.map { "\($0.0) - \($0.1)" }
// outputs ["Bob - 15", "Dave - 15"]
You can zip the collection indices with its elements and get the minimum value using collection min method and pass a predicate to compare the elements. Get the result and extract the index of the tuple:
let numbers = [2, 4, 4, 2, 3, 1]
let minIndex = zip(numbers.indices, numbers).min(by: { $0.1 < $1.1 })?.0 // 5
let maxIndex = zip(numbers.indices, numbers).max(by: { $0.1 < $1.1 })?.0 // 1
As an extension where the elements are comparable:
extension Collection where Element: Comparable {
func firstIndexOfMaxElement() -> Index? {
zip(indices, self).max(by: { $0.1 < $1.1 })?.0
}
func firstIndexOfMinElement() -> Index? {
zip(indices, self).min(by: { $0.1 < $1.1 })?.0
}
}
Usage:
numbers.firstIndexOfMinElement() // 5
If you need to find the maximum or minimum properties:
extension Collection {
func firstIndexOfMaxElement<T: Comparable>(_ predicate: (Element) -> T) -> Index? {
zip(indices, self).max(by: { predicate($0.1) < predicate($1.1) })?.0
}
func firstIndexOfMinElement<T: Comparable>(_ predicate: (Element) -> T) -> Index? {
zip(indices, self).min(by: { predicate($0.1) < predicate($1.1) })?.0
}
}
Usage:
struct Product {
let price: Int
}
let products: [Product] = [.init(price: 2),
.init(price: 4),
.init(price: 4),
.init(price: 2),
.init(price: 3),
.init(price: 1),]
let minPrice = products.firstIndexOfMinElement(\.price) // 5
To return the maximum and minimum elements and their indices:
extension Collection where Element: Comparable {
func maxElementAndIndices() -> (indices: [Index], element: Element)? {
guard let maxValue = self.max() else { return nil }
return (indices.filter { self[$0] == maxValue }, maxValue)
}
func minElementAndIndices() -> (indices: [Index], element: Element)? {
guard let minValue = self.min() else { return nil }
return (indices.filter { self[$0] == minValue }, minValue)
}
}
And the corresponding methods to custom structures/classes:
extension Collection {
func maxElementsAndIndices<T: Comparable>(_ predicate: (Element) -> T) -> [(index: Index, element: Element)] {
guard let maxValue = self.max(by:{ predicate($0) < predicate($1)}) else { return [] }
return zip(indices, self).filter { predicate(self[$0.0]) == predicate(maxValue) }
}
func minElementsAndIndices<T: Comparable>(_ predicate: (Element) -> T) -> [(index: Index, element: Element)] {
guard let minValue = self.min(by:{ predicate($0) < predicate($1)}) else { return [] }
return zip(indices, self).filter { predicate(self[$0.0]) == predicate(minValue) }
}
}
Usage:
let maxNumbers = numbers.maxElementAndIndices() // ([1, 2], element 4)
let minNumbers = numbers.minElementAndIndices() // ([5], element 1)
let maxPriceIndices = products.maxElementsAndIndices(\.price) // [(index: 1, element: Product(price: 4)), (index: 2, element: Product(price: 4))]
let minPriceIndices = products.minElementsAndIndices(\.price) // [(index: 5, element: __lldb_expr_22.Product(price: 1))]
There are a couple of ways to solve your problem, you can solve this by saving the indices of scores.max() and iterate through the players list, and also using then zip function:
var max_score = scores.max()
var players_and_score = zip(players, scores)
for player in players_and_score{
if player.1 == max_score{
print(player.0)
}
}

Sort Through Array Excluding Nil

I am attempting to sort an array which includes variables which are nil.
class TestArray {
var a: String? = nil
var b: String? = nil
init(a: String?, b: String?) {
self.a = a
self.b = b
}
}
let first = TestArray(a: "xxx", b: "yyy")
let second = TestArray(a: "zzz", b: "zzz")
let third = TestArray(a: "aaa", b: nil)
var array = [first, second, third]
array.sort(by: {($0.a as String!) > ($1.a as String!)})
array.sort(by: {($0.b as String!) > ($1.b as String!)}) // Throws an error
How can I sort by b and leave the third TestArray with b = nil at the end of the array?
Also I would like to sort through all b and then for the arrays in which b = nil sort the remaining by a.
I believe this should cover the situations you want without having to iterate more than once (which is expensive)
array.sort { (lessThan, item) -> Bool in
switch (lessThan.a, lessThan.b, item.a, item.b) {
// if bs exist, use that comparison
case (_, .some(let lessThanB), _, .some(let itemB)):
return lessThanB < itemB
// if bs don't exist by as do, use that comparison
case (.some(let lessThanA), .none, .some(let itemA), .none):
return lessThanA < itemA
// if one item has a value but the other doesn't, the item with the value should be first
case (_, .some(_), _, .none), (.some(_), _, .none, _ ):
return true
default:
return false
}
}
Here's (in my opinion) the best way:
First, I define a < operator for String? instances.
It operates for the specification that nil compares as less than any other string, so it'll appear first in sorting order.
fileprivate func <(a: String?, b: String?) -> Bool {
switch (a, b) {
case (nil, nil): return false
case (nil, _?): return true
case (_?, nil): return false
case (let a?, let b?): return a < b
}
}
Next, I wrote a sorting predicate that uses that operator. It works for the specification that instances are to be sorted first by their a. If those are equal, ties are broken by sorting by their b.
struct TestStruct {
var a: String?
var b: String?
}
let input = [
TestStruct(a: "xxx", b: "yyy"),
TestStruct(a: "zzz", b: "zzz"),
TestStruct(a: "aaa", b: nil)
]
let output = input.sorted { lhs, rhs in
if lhs.a != rhs.a { return lhs.a < rhs.a } // first sort by a
if lhs.b != rhs.b { return lhs.b < rhs.b } // then sort by b
return true
}
print(output)
//[
// TempCode.TestStruct(a: Optional("aaa"), b: nil),
// TempCode.TestStruct(a: Optional("xxx"), b: Optional("yyy")),
// TempCode.TestStruct(a: Optional("zzz"), b: Optional("zzz"))
//]
*I made TestStruct a struct just so I can use the synthesized initializer and automatic print behaviour. It'll work just the same if it's a class.
If you'll be comparing TestStruct a lot, and it makes sense for there to be a standard order to comparing it, then it's best you add conformance to Comparable. Doing so also requires conformance to Equatable:
extension TestStruct: Equatable {
public static func ==(lhs: TestStruct, rhs: TestStruct) -> Bool {
return
lhs.a == rhs.a &&
lhs.b == rhs.b
}
}
extension TestStruct: Comparable {
public static func <(lhs: TestStruct, rhs: TestStruct) -> Bool {
if lhs.a != rhs.a { return lhs.a < rhs.a } // first sort by a
if lhs.b != rhs.b { return lhs.b < rhs.b } // then sort by b
return true
}
}
Now you can sort your array easily with the sorting behaviour specified by TestStruct:
let output = input.sorted()
You can see it in action here.
In case you needn't necessarily sort in place, you could simply apply the "algorithm" you describe in a brute-force fashion to sort array using several filter followed sorted for the subset arrays.
"Algorithm":
Primarily sort by b property, given that it is not nil.
For elements where the b property is nil, sort by a property.
Leave elements where both a and b are nil at the end of the sorted array.
E.g.:
// lets set up a more interesting example
let first = TestArray(a: "xxx", b: "yyy")
let second = TestArray(a: "ccc", b: nil)
let third = TestArray(a: "aaa", b: nil)
let fourth = TestArray(a: "zzz", b: "zzz")
let fifth = TestArray(a: "bbb", b: nil)
var array = [first, second, third, fourth, fifth]
let sorted = array.filter { $0.b != nil }.sorted { $0.b! < $1.b! } +
array.filter { $0.b == nil && $0.a != nil }.sorted { $0.a! < $1.a! } +
array.filter { $0.b == nil && $0.a == nil }
sorted.enumerated().forEach { print("\($0): b: \($1.b), a: \($1.a)") }
/* 0: b: Optional("yyy"), a: Optional("xxx")
1: b: Optional("zzz"), a: Optional("zzz")
2: b: nil, a: Optional("aaa")
3: b: nil, a: Optional("bbb")
4: b: nil, a: Optional("ccc") */

count numbers in array and order them by count in swift

Is there a easy way to sort an array by the count of numbers? And if a number have the same count put the highest number first.
[2,8,2,6,1,8,2,6,6]
to
[6,6,6,2,2,2,8,8,1]
What you are looking for is a way to get the frequencies of values.
As long as the values are Hashable this function will work:
It extends all sequence types where the Element is Hashable, so an array of Int will work.
extension SequenceType where Generator.Element : Hashable {
func frequencies() -> [Generator.Element:Int] {
var results : [Generator.Element:Int] = [:]
for element in self {
results[element] = (results[element] ?? 0) + 1
}
return results
}
}
Then you can do this:
let alpha = [2,8,2,6,1,8,2,6,6]
let sorted = alpha.frequencies().sort {
if $0.1 > $1.1 { // if the frequency is higher, return true
return true
} else if $0.1 == $1.1 { // if the frequency is equal
return $0.0 > $1.0 // return value is higher
} else {
return false // else return false
}
}
Even better, you can now create another extension to sequence types.
Now they need to conform to Comparable as well as Hashable
extension SequenceType where Generator.Element : protocol<Hashable,Comparable> {
func sortByFrequency() -> [Generator.Element] {
// the same sort function as before
let sorted = self.frequencies().sort {
if $0.1 > $1.1 {
return true
} else if $0.1 == $1.1 {
return $0.0 > $1.0
} else {
return false
}
}
// this is to convert back from the dictionary to an array
var sortedValues : [Generator.Element] = []
sorted.forEach { // for each time the value was found
for _ in 0..<$0.1 {
sortedValues.append($0.0) // append
}
}
return sortedValues
}
}
Your final usage of all this will look like this :
let sorted = alpha.sortByFrequency() // [6, 6, 6, 2, 2, 2, 8, 8, 1]
Super clean :)
If you prefer a function closer to sort itself you can also use this :
extension SequenceType where Generator.Element : Hashable {
func sortedFrequency(#noescape isOrderedBefore: ((Self.Generator.Element,Int), (Self.Generator.Element,Int)) -> Bool) -> [Generator.Element] {
let sorted = self.frequencies().sort {
return isOrderedBefore($0,$1) // this uses the closure to sort
}
var sortedValues : [Generator.Element] = []
sorted.forEach {
for _ in 0..<$0.1 {
sortedValues.append($0.0)
}
}
return sortedValues
}
}
The extension above converts the array to a frequency dictionary internally and just asks you to input a closure that returns a Bool. Then you can apply different sorting depending on your needs.
Because you pass the closure with the sorting logic to this function the Elements of the SequenceType no longer need to be comparable.
Cheat sheet for all the shorthand:
$0 // first element
$1 // second element
$0.0 // value of first element
$0.1 // frequency of first element
Sorting :
let sortedB = alpha.sortedFrequency {
if $0.1 > $1.1 {
return true
} else if $0.1 == $1.1 {
return $0.0 > $1.0
} else {
return false
}
} // [6, 6, 6, 2, 2, 2, 8, 8, 1]
I'm not sure if this is the most efficient way to do it, but I think it is fairly elegant:
extension Array where Element: Equatable {
func subArrays() -> [[Element]] {
if self.isEmpty {
return [[]]
} else {
let slice = self.filter { $0 == self[0] }
let rest = self.filter { $0 != self[0] }
return rest.isEmpty
? [slice]
: [slice] + rest.subArrays()
}
}
func sortByFrequency(secondarySort: ((Element, Element) -> Bool)? = nil) -> [Element] {
return self.subArrays()
.sort { secondarySort?($0[0], $1[0]) ?? false }
.sort { $0.count > $1.count }
.flatMap { $0 }
}
}
let nums = [2,8,2,6,1,8,2,6,6]
print(nums.sortByFrequency(>)) // [6, 6, 6, 2, 2, 2, 8, 8, 1]
The function subArrays just breaks the array down into an array of sub-arrays for each value in the original array - i.e., you'd get [[2,2,2],[8,8],[6,6,6],[1]] for the input that you provided.
sortByFrequency sorts the output of subArrays and then flatMaps to get the answer.
EDIT: I modified sortByFrequency to add the optional secondarySearch parameter. That allows you to control how you want items that occur at the same frequency to be sorted. Or, just accept the default nil and they won't be sorted by anything other than frequency.
Also, I modified the extension to indicate that Element only needs to conform to Equatable, not Comparable.
//: Playground - noun: a place where people can play
import UIKit
var arr1 = [2,8,2,6,1,8,2,6,6]
var arr2 = [6,6,6,2,2,2,8,8,1]
var counting = [Int: Int]()
// fill counting dictionary
for num in arr1 {
if counting[num] != nil {
counting[num]!++
} else {
counting[num] = 1
}
}
// [6: 3, 2: 3, 8: 2, 1: 1]
print(counting)
func order(i1: Int, i2: Int) -> Bool {
let count1 = counting[i1]
let count2 = counting[i2]
// if counting is the same: compare which number is greater
if count1 == count2 {
return i1 > i2
} else {
return count1 > count2
}
}
// [6, 6, 6, 2, 2, 2, 8, 8, 1]
print(arr1.sort(order))
print(arr2)
Using grouping in Dictionary:
var entries = [1,2,3,3,1,3,5,6,3,4,1,5,5,5,5]
extension Sequence where Element : Hashable {
func byFrequency() -> [Element] {
Dictionary(grouping: self, by: {$0}).sorted{ (a, b) in
a.value.count > b.value.count
}.map { $0.key}
}
}
print(entries.byFrequency().first)
Prints 5

How to sort an array of custom objects by property value in Swift

Let's say we have a custom class named imageFile and this class contains two properties:
class imageFile {
var fileName = String()
var fileID = Int()
}
Lots of them are stored in an Array:
var images : Array = []
var aImage = imageFile()
aImage.fileName = "image1.png"
aImage.fileID = 101
images.append(aImage)
aImage = imageFile()
aImage.fileName = "image1.png"
aImage.fileID = 202
images.append(aImage)
How can I sort the images array by 'fileID' in ascending or descending order?
First, declare your Array as a typed array so that you can call methods when you iterate:
var images : [imageFile] = []
Then you can simply do:
Swift 2
images.sorted({ $0.fileID > $1.fileID })
Swift 3
images.sorted(by: { $0.fileID > $1.fileID })
Swift 5
images.sorted { $0.fileId > $1.fileID }
The example above gives the results in descending order.
[Updated for Swift 3 with sort(by:)] This, exploiting a trailing closure:
images.sorted { $0.fileID < $1.fileID }
where you use < or > depending on ASC or DESC, respectively. If you want to modify the images array, then use the following:
images.sort { $0.fileID < $1.fileID }
If you are going to do this repeatedly and prefer to define a function, one way is:
func sorterForFileIDASC(this:imageFile, that:imageFile) -> Bool {
return this.fileID < that.fileID
}
and then use as:
images.sort(by: sorterForFileIDASC)
Swift 3
people = people.sorted(by: { $0.email > $1.email })
With Swift 5, Array has two methods called sorted() and sorted(by:). The first method, sorted(), has the following declaration:
Returns the elements of the collection, sorted.
func sorted() -> [Element]
The second method, sorted(by:), has the following declaration:
Returns the elements of the collection, sorted using the given predicate as the comparison between elements.
func sorted(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows -> [Element]
#1. Sort with ascending order for comparable objects
If the element type inside your collection conforms to Comparable protocol, you will be able to use sorted() in order to sort your elements with ascending order. The following Playground code shows how to use sorted():
class ImageFile: CustomStringConvertible, Comparable {
let fileName: String
let fileID: Int
var description: String { return "ImageFile with ID: \(fileID)" }
init(fileName: String, fileID: Int) {
self.fileName = fileName
self.fileID = fileID
}
static func ==(lhs: ImageFile, rhs: ImageFile) -> Bool {
return lhs.fileID == rhs.fileID
}
static func <(lhs: ImageFile, rhs: ImageFile) -> Bool {
return lhs.fileID < rhs.fileID
}
}
let images = [
ImageFile(fileName: "Car", fileID: 300),
ImageFile(fileName: "Boat", fileID: 100),
ImageFile(fileName: "Plane", fileID: 200)
]
let sortedImages = images.sorted()
print(sortedImages)
/*
prints: [ImageFile with ID: 100, ImageFile with ID: 200, ImageFile with ID: 300]
*/
#2. Sort with descending order for comparable objects
If the element type inside your collection conforms to Comparable protocol, you will have to use sorted(by:) in order to sort your elements with a descending order.
class ImageFile: CustomStringConvertible, Comparable {
let fileName: String
let fileID: Int
var description: String { return "ImageFile with ID: \(fileID)" }
init(fileName: String, fileID: Int) {
self.fileName = fileName
self.fileID = fileID
}
static func ==(lhs: ImageFile, rhs: ImageFile) -> Bool {
return lhs.fileID == rhs.fileID
}
static func <(lhs: ImageFile, rhs: ImageFile) -> Bool {
return lhs.fileID < rhs.fileID
}
}
let images = [
ImageFile(fileName: "Car", fileID: 300),
ImageFile(fileName: "Boat", fileID: 100),
ImageFile(fileName: "Plane", fileID: 200)
]
let sortedImages = images.sorted(by: { (img0: ImageFile, img1: ImageFile) -> Bool in
return img0 > img1
})
//let sortedImages = images.sorted(by: >) // also works
//let sortedImages = images.sorted { $0 > $1 } // also works
print(sortedImages)
/*
prints: [ImageFile with ID: 300, ImageFile with ID: 200, ImageFile with ID: 100]
*/
#3. Sort with ascending or descending order for non-comparable objects
If the element type inside your collection DOES NOT conform to Comparable protocol, you will have to use sorted(by:) in order to sort your elements with ascending or descending order.
class ImageFile: CustomStringConvertible {
let fileName: String
let fileID: Int
var description: String { return "ImageFile with ID: \(fileID)" }
init(fileName: String, fileID: Int) {
self.fileName = fileName
self.fileID = fileID
}
}
let images = [
ImageFile(fileName: "Car", fileID: 300),
ImageFile(fileName: "Boat", fileID: 100),
ImageFile(fileName: "Plane", fileID: 200)
]
let sortedImages = images.sorted(by: { (img0: ImageFile, img1: ImageFile) -> Bool in
return img0.fileID < img1.fileID
})
//let sortedImages = images.sorted { $0.fileID < $1.fileID } // also works
print(sortedImages)
/*
prints: [ImageFile with ID: 300, ImageFile with ID: 200, ImageFile with ID: 100]
*/
Note that Swift also provides two methods called sort() and sort(by:) as counterparts of sorted() and sorted(by:) if you need to sort your collection in-place.
Nearly everyone gives how directly, let me show the evolvement:
you can use the instance methods of Array:
// general form of closure
images.sortInPlace({ (image1: imageFile, image2: imageFile) -> Bool in return image1.fileID > image2.fileID })
// types of closure's parameters and return value can be inferred by Swift, so they are omitted along with the return arrow (->)
images.sortInPlace({ image1, image2 in return image1.fileID > image2.fileID })
// Single-expression closures can implicitly return the result of their single expression by omitting the "return" keyword
images.sortInPlace({ image1, image2 in image1.fileID > image2.fileID })
// closure's argument list along with "in" keyword can be omitted, $0, $1, $2, and so on are used to refer the closure's first, second, third arguments and so on
images.sortInPlace({ $0.fileID > $1.fileID })
// the simplification of the closure is the same
images = images.sort({ (image1: imageFile, image2: imageFile) -> Bool in return image1.fileID > image2.fileID })
images = images.sort({ image1, image2 in return image1.fileID > image2.fileID })
images = images.sort({ image1, image2 in image1.fileID > image2.fileID })
images = images.sort({ $0.fileID > $1.fileID })
For elaborate explanation about the working principle of sort, see The Sorted Function.
In Swift 3.0
images.sort(by: { (first: imageFile, second: imageFile) -> Bool in
first. fileID < second. fileID
})
You can also do something like
images = sorted(images) {$0.fileID > $1.fileID}
so your images array will be stored as sorted
Swift 4.0, 4.1 & 4.2:
First, I created mutable array of type imageFile as shown below
var arr = [imageFile]()
Create mutable object image of type imageFile and assign value to properties as shown below
var image = imageFile()
image.fileId = 14
image.fileName = "A"
Now, append this object to array arr
arr.append(image)
Now, assign the different properties to same mutable object i.e image
image = imageFile()
image.fileId = 13
image.fileName = "B"
Now, again append image object to array arr
arr.append(image)
Now, we will apply Ascending order on fileId property in array arr objects. Use < symbol for ascending order
arr = arr.sorted(by: {$0.fileId < $1.fileId}) // arr has all objects in Ascending order
print("sorted array is",arr[0].fileId)// sorted array is 13
print("sorted array is",arr[1].fileId)//sorted array is 14
Now, we will apply Descending order on on fileId property in array arr objects. Use > symbol for Descending order
arr = arr.sorted(by: {$0.fileId > $1.fileId}) // arr has all objects in Descending order
print("Unsorted array is",arr[0].fileId)// Unsorted array is 14
print("Unsorted array is",arr[1].fileId)// Unsorted array is 13
In Swift 4.1 & 4.2, for sorted order use
let sortedArr = arr.sorted { (id1, id2) -> Bool in
return id1.fileId < id2.fileId // Use > for Descending order
}
Swift 2 through 4
The original answer sought to sort an array of custom objects using some property. Below I will show you a few handy ways to do this same behavior w/ swift data structures!
Little things outta the way, I changed ImageFile ever so slightly. With that in mind, I create an array with three image files. Notice that metadata is an optional value, passing in nil as a parameter is expected.
struct ImageFile {
var name: String
var metadata: String?
var size: Int
}
var images: [ImageFile] = [ImageFile(name: "HelloWorld", metadata: nil, size: 256), ImageFile(name: "Traveling Salesmen", metadata: "uh this is huge", size: 1024), ImageFile(name: "Slack", metadata: "what's in this stuff?", size: 2048) ]
ImageFile has a property named size. For the following examples I will show you how to use sort operations w/ properties like size.
smallest to biggest size (<)
let sizeSmallestSorted = images.sorted { (initial, next) -> Bool in
return initial.size < next.size
}
biggest to smallest (>)
let sizeBiggestSorted = images.sorted { (initial, next) -> Bool in
return initial.size > next.size
}
Next we'll sort using the String property name. In the same manner, use sort to compare strings. But notice the inner block returns a comparison result. This result will define sort.
A-Z (.orderedAscending)
let nameAscendingSorted = images.sorted { (initial, next) -> Bool in
return initial.name.compare(next.name) == .orderedAscending
}
Z-A (.orderedDescending)
let nameDescendingSorted = images.sorted { (initial, next) -> Bool in
return initial.name.compare(next.name) == .orderedDescending
}
Next is my favorite way to sort, in many cases one will have optional properties. Now don't worry, we're going to sort in the same manner as above except we have to handle nil! In production;
I used this code to force all instances in my array with nil property values to be last. Then order metadata using the assumed unwrapped values.
let metadataFirst = images.sorted { (initial, next) -> Bool in
guard initial.metadata != nil else { return true }
guard next.metadata != nil else { return true }
return initial.metadata!.compare(next.metadata!) == .orderedAscending
}
It is possible to have a secondary sort for optionals. For example; one could show images with metadata and ordered by size.
Two alternatives
1) Ordering the original array with sortInPlace
self.assignments.sortInPlace({ $0.order < $1.order })
self.printAssignments(assignments)
2) Using an alternative array to store the ordered array
var assignmentsO = [Assignment] ()
assignmentsO = self.assignments.sort({ $0.order < $1.order })
self.printAssignments(assignmentsO)
You return a sorted array from the fileID property by following way:
Swift 2
let sortedArray = images.sorted({ $0.fileID > $1.fileID })
Swift 3 OR 4
let sortedArray = images.sorted(by: { $0.fileID > $1.fileID })
Swift 5.0
let sortedArray = images.sorted {
$0.fileID < $1.fileID
}
If you are going to be sorting this array in more than one place, it may make sense to make your array type Comparable.
class MyImageType: Comparable, Printable {
var fileID: Int
// For Printable
var description: String {
get {
return "ID: \(fileID)"
}
}
init(fileID: Int) {
self.fileID = fileID
}
}
// For Comparable
func <(left: MyImageType, right: MyImageType) -> Bool {
return left.fileID < right.fileID
}
// For Comparable
func ==(left: MyImageType, right: MyImageType) -> Bool {
return left.fileID == right.fileID
}
let one = MyImageType(fileID: 1)
let two = MyImageType(fileID: 2)
let twoA = MyImageType(fileID: 2)
let three = MyImageType(fileID: 3)
let a1 = [one, three, two]
// return a sorted array
println(sorted(a1)) // "[ID: 1, ID: 2, ID: 3]"
var a2 = [two, one, twoA, three]
// sort the array 'in place'
sort(&a2)
println(a2) // "[ID: 1, ID: 2, ID: 2, ID: 3]"
If you are not using custom objects, but value types instead that implements Comparable protocol (Int, String etc..) you can simply do this:
myArray.sort(>) //sort descending order
An example:
struct MyStruct: Comparable {
var name = "Untitled"
}
func <(lhs: MyStruct, rhs: MyStruct) -> Bool {
return lhs.name < rhs.name
}
// Implementation of == required by Equatable
func ==(lhs: MyStruct, rhs: MyStruct) -> Bool {
return lhs.name == rhs.name
}
let value1 = MyStruct()
var value2 = MyStruct()
value2.name = "A New Name"
var anArray:[MyStruct] = []
anArray.append(value1)
anArray.append(value2)
anArray.sort(>) // This will sort the array in descending order
If the array elements conform to Comparable, then you can simply use functional syntax:
array.sort(by: <)
If you're sorting based on a custom type, all you need to do is implement the < operator:
class ImageFile {
let fileName: String
let fileID: Int
let fileSize: Int
static func < (left: ImageFile, right: ImageFile) -> Bool {
return left.fileID < right.fileID
}
}
However, sometimes you don't want one standard way of comparing ImageFiles. Maybe in some contexts you want to sort images based on fileID, and elsewhere you want to sort based on fileSize. For dynamic comparisons, you have two options.
sorted(by:)
images = images.sorted(by: { a, b in
// Return true if `a` belongs before `b` in the sorted array
if a.fileID < b.fileID { return true }
if a.fileID > b.fileID { return false }
// Break ties by comparing file sizes
return a.fileSize > b.fileSize
})
You can simplify the syntax using a trailing closure:
images.sorted { ... }
But manually typing if statements can lead to long code (if we wanted to break file size ties by sorting based on file names, we would have quite an if chain of doom). We can avoid this syntax by using the brand-new SortComparator protocol (macOS 12+, iOS 15+):
sorted(using:)
files = files.sorted(using: [
KeyPathComparator(\.fileID, order: .forward),
KeyPathComparator(\.fileSize, order: .reverse),
])
This code sorts the files based on their file ID (.forward means ascending) and breaks ties by sorting based on file size (.reverse means descending). The \.fileID syntax is how we specify key paths. You can expand the list of comparators as much as you need.
Swift 3,4,5
struct imageFile {
var fileName = String()
var fileID = Int()
}
//append objects like this
var arrImages = [imageFile]()
arrImages.append(.init(fileName: "Hello1.png", fileID: 1))
arrImages.append(.init(fileName: "Hello3.png", fileID: 3))
arrImages.append(.init(fileName: "Hello2.png",fileID: 2))
//array sorting using below code
let sortImagesArr = arrImages.sorted(by: {$0.fileID < $1.fileID})
print(sortImagesArr)
//output
imageFile(fileName: "Hello1.png", fileID: 1),
imageFile(fileName: "Hello2.png", fileID: 2),
imageFile(fileName: "Hello3.png", fileID: 3)
I do it like this and it works:
var images = [imageFile]()
images.sorted(by: {$0.fileID.compare($1.fileID) == .orderedAscending })
If you want to sort original array of custom objects. Here is another way to do so in Swift 2.1
var myCustomerArray = [Customer]()
myCustomerArray.sortInPlace {(customer1:Customer, customer2:Customer) -> Bool in
customer1.id < customer2.id
}
Where id is an Integer. You can use the same < operator for String properties as well.
You can learn more about its use by looking at an example here:
Swift2: Nearby Customers
var students = ["Kofi", "Abena", "Peter", "Kweku", "Akosua"]
students.sort(by: >)
print(students)
Prints : "["Peter", "Kweku", "Kofi", "Akosua", "Abena"]"
Sort using KeyPath
you can sort by KeyPath like this:
myArray.sorted(by: \.fileName, <) /* using `<` for ascending sorting */
By implementing this little helpful extension.
extension Collection{
func sorted<Value: Comparable>(
by keyPath: KeyPath<Element, Value>,
_ comparator: (_ lhs: Value, _ rhs: Value) -> Bool) -> [Element] {
sorted { comparator($0[keyPath: keyPath], $1[keyPath: keyPath]) }
}
}
Hope Swift add this in the near future in the core of the language.
Swift 3 & 4 & 5
I had some problem related to lowercase and capital case
so I did this code
let sortedImages = images.sorted(by: { $0.fileID.lowercased() < $1.fileID.lowercased() })
and then use sortedImages after that

Resources