Related
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
NSArray has - (NSUInteger)indexOfObject:(id)obj inSortedRange:(NSRange)r options:(NSBinarySearchingOptions)opts usingComparator:(NSComparator)cmp to determine the insert position of a new object in a sorted array.
What is the best and high-performance way to do this in pure Swift?
Something along the lines of:
var myArray = ["b", "e", "d", "a"]
myArray.sort { $0 < $1 }
// myArray is now [a, b, d, e]
myArray.append("c")
myArray.sort { $0 < $1 }
// myArray is now [a, b, c, d, e]
Instead of appending the new element and then sorting the array, I would like to figure out the correct position and insert the element:
let index = [... how to calculate this index ??? ...]
myArray.insert("c", atIndex: index)
Here is a possible implementation in Swift using binary search (from
http://rosettacode.org/wiki/Binary_search#Swift with slight modifications):
extension Array {
func insertionIndexOf(_ elem: Element, isOrderedBefore: (Element, Element) -> Bool) -> Int {
var lo = 0
var hi = self.count - 1
while lo <= hi {
let mid = (lo + hi)/2
if isOrderedBefore(self[mid], elem) {
lo = mid + 1
} else if isOrderedBefore(elem, self[mid]) {
hi = mid - 1
} else {
return mid // found at position mid
}
}
return lo // not found, would be inserted at position lo
}
}
As with indexOfObject:inSortedRange:options:usingComparator: it is assumed that
the array is sorted with respect to the comparator.
It returns either (any) index of the element if the element is already present in the
array, or the index where it can be inserted while preserving the order. This
corresponds to the NSBinarySearchingInsertionIndex of the NSArray method.
Usage:
let newElement = "c"
let index = myArray.insertionIndexOf(newElement) { $0 < $1 } // Or: myArray.indexOf(c, <)
myArray.insert(newElement, at: index)
In swift 3 you can use index(where:):
var myArray = ["a", "b", "d", "e"]
let newElement = "c"
if let index = myArray.index(where: { $0 > newElement }) {
myArray.insert(newElement, at: index)
}
Note that in this case you need to reverse the condition inside the closure (i.e. > instead of < for increasing elements in array), because the index you are interested in is the first element that does NOT match the predicate. Also, this method will return nil if the newly inserted element is going to be the last in the array (newElement = "z" in the example above.
For convenience, this can be wrapped to a separate function that will handle all these issues:
extension Collection {
func insertionIndex(of element: Self.Iterator.Element,
using areInIncreasingOrder: (Self.Iterator.Element, Self.Iterator.Element) -> Bool) -> Index {
return index(where: { !areInIncreasingOrder($0, element) }) ?? endIndex
}
}
Usage:
var myArray = ["a", "b", "d", "e"]
let newElement = "c"
let index = myArray.insertionIndex(of: newElement, using: <)
myArray.insert(newElement, at: index)
According to WWDC 2018 Session 406: Swift Generics (Expanded) the binary search can be performed in a more efficient and even more generic way by slicing the collection object.
There are two considerable benefits of Slice:
A slice is always a subset of the original object without allocating additional memory.
All indices of the slice refer to the original object.
For example if you slice an array of 5 objects let slice = array[2..<4] then slice.startIndex is 2 not 0.
RandomAccessCollection is a protocol (inherited from BidirectionalCollection) which a variety of structs / classes conform to
extension RandomAccessCollection where Element : Comparable {
func insertionIndex(of value: Element) -> Index {
var slice : SubSequence = self[...]
while !slice.isEmpty {
let middle = slice.index(slice.startIndex, offsetBy: slice.count / 2)
if value < slice[middle] {
slice = slice[..<middle]
} else {
slice = slice[index(after: middle)...]
}
}
return slice.startIndex
}
}
Example:
let array = [1, 2, 4, 7, 8]
let index = array.insertionIndex(of: 6) // 3
You can extend the function to check against a predicate closure instead of a single value
extension RandomAccessCollection { // the predicate version is not required to conform to Comparable
func insertionIndex(for predicate: (Element) -> Bool) -> Index {
var slice : SubSequence = self[...]
while !slice.isEmpty {
let middle = slice.index(slice.startIndex, offsetBy: slice.count / 2)
if predicate(slice[middle]) {
slice = slice[index(after: middle)...]
} else {
slice = slice[..<middle]
}
}
return slice.startIndex
}
}
Example:
struct Person { let name : String }
let array = [Person(name: "Adam"), Person(name: "Cynthia"), Person(name: "Nancy"), Person(name: "Tom")]
let index = array.insertionIndex{ $0.name < "Bruce" } // 1
If you know your array is sorted, you can use this method -- it will work with arrays of any type. It will traverse the whole array each time, so don't use this with large arrays - go for another data type if you have larger needs!
func insertSorted<T: Comparable>(inout seq: [T], newItem item: T) {
let index = seq.reduce(0) { $1 < item ? $0 + 1 : $0 }
seq.insert(item, atIndex: index)
}
var arr = [2, 4, 6, 8]
insertSorted(&arr, newItem: 5)
insertSorted(&arr, newItem: 3)
insertSorted(&arr, newItem: -3)
insertSorted(&arr, newItem: 11)
// [-3, 2, 3, 4, 5, 6, 8, 11]
Building on #vadian's and #Martin R's answers, I noticed some minor discrepancies, mostly with the insertion index either not matching the index of an equivalent element in the collection, or of it not matching the first index of a sequence of equivalent elements.
For instance:
If you wanted to find an insertion index for 5 in [4, 5, 6], the index 2 would be returned, which may be problematic if you want to simply search for the value.
In [5, 5, 5], searching once again for 5 returns the index 1, which is not the first insertion index.
This doesn't match with the behavior of NSArray's implementation and its various options, so here is yet another solution that tries to take this into account:
extension RandomAccessCollection {
/// Get the index of or an insertion index for a new element in
/// a sorted collection in ascending order.
/// - Parameter value: The element to insert into the array.
/// - Returns: The index suitable for inserting the new element
/// into the array, or the first index of an existing element.
#inlinable
public func sortedInsertionIndex(
of element: Element
) -> Index where Element: Comparable {
sortedInsertionIndex(of: element, by: <)
}
/// Get the index of or an insertion index for a new element in
/// a sorted collection that matches the rule defined by the predicate.
/// - Parameters:
/// - value: The element to insert into the array.
/// - areInIncreasingOrder:
/// A closure that determines if the first element should
/// come before the second element. For instance: `<`.
/// - Returns: The index suitable for inserting the new element
/// into the array, or the first index of an existing element.
#inlinable
public func sortedInsertionIndex(
of element: Element,
by areInIncreasingOrder: (Element, Element) throws -> Bool
) rethrows -> Index {
try sortedInsertionIndex { try areInIncreasingOrder($0, element) }
}
/// Get the index of or an insertion index for a new element in
/// a sorted collection that matches the rule defined by the predicate.
///
/// This variation is useful when comparing an element that
/// is of a different type than those already in the array.
/// - Parameter isOrderedAfter:
/// Return `true` if the new element should come after the one
/// provided in the closure, or `false` otherwise. For instance
/// `{ $0 < newElement }` to sort elements in increasing order.
/// - Returns: The index suitable for inserting the new element into
/// the array, or the first index of an existing element.
#inlinable
public func sortedInsertionIndex(
where isOrderedAfter: (Element) throws -> Bool
) rethrows -> Index {
var slice: SubSequence = self[...]
while !slice.isEmpty {
let middle = slice.index(slice.startIndex, offsetBy: slice.count/2)
if try isOrderedAfter(slice[middle]) {
slice = slice[index(after: middle)...]
} else {
slice = slice[..<middle]
}
}
return slice.startIndex
}
}
Since sometimes you don't care about the insertion index, but instead the first or last index that matches a given element, here are variations on the above that satisfy those requirements as well:
extension RandomAccessCollection {
#inlinable
public func sortedFirstIndex(
of element: Element
) -> Index? where Element: Comparable {
sortedFirstIndex(of: element, by: <)
}
#inlinable
public func sortedFirstIndex(
of element: Element,
by areInIncreasingOrder: (Element, Element) throws -> Bool
) rethrows -> Index? where Element: Comparable {
let insertionIndex = try sortedInsertionIndex(of: element, by: areInIncreasingOrder)
guard insertionIndex < endIndex, self[insertionIndex] == element else { return nil }
return insertionIndex
}
#inlinable
public func sortedLastIndex(
of element: Element
) -> Index? where Element: Comparable {
sortedLastIndex(of: element, by: <)
}
#inlinable
public func sortedLastIndex(
of element: Element,
by areInIncreasingOrder: (Element, Element) throws -> Bool
) rethrows -> Index? where Element: Comparable {
let insertionIndex = try sortedInsertionIndex(of: element) { try areInIncreasingOrder($1, $0) }
let finalIndex = index(insertionIndex, offsetBy: -1)
guard finalIndex >= startIndex, self[finalIndex] == element else { return nil }
return finalIndex
}
}
A binary search tree here is the way to go.
On an ordered array, take the element in the middle and see if the object at that position is greater than your new object. That way you can forget the half of the array elements with one single comparison.
Repeat that step with the remaining half. Again, with a single comparison you can forget the half of the remaining objects. Your target element count is now a quarter of the array size at the beginning with only two comparisons.
Repeat that until you found the correct position to insert the new element.
Here is a a good article on binary search trees with swift
In swift 5:
var myArray = ["b", "e", "d", "a"]
myArray.sort { $0 < $1 }
// myArray is now [a, b, d, e]
let newElement = "c"
let index = myArray.firstIndex(where: { newElement < $0 })
myArray.insert(newElement, at: index ?? myArray.endIndex)
If you'd like to make the call-site more pretty:
extension Array where Element: Comparable {
/// Insert element in the correct location of a sorted array
mutating func insertSorted(_ element: Element) {
let index = firstIndex(where: { element < $0 })
insert(element, at: index ?? endIndex)
}
}
myArray.insertSorted("c")
Slight update from binary search:
extension Array {
mutating func binaryAppend(_ item:Element, sortBy: (Element,Element) -> Bool) {
var start:Int = 0
var end:Int = self.count - 1
while start <= end{
let mid = (start + end) / 2
if sortBy(self[mid], item){
start = start + 1
} else{
end = end - 1
}
}
self.insert(item, at: start)
}
}
You can use it like:
var arr = [1,3,5,7,9]
arr.binaryAppend(2, sortBy: {$0 < $1}) //[1,2,3,5,7,9]
NSArray has - (NSUInteger)indexOfObject:(id)obj inSortedRange:(NSRange)r options:(NSBinarySearchingOptions)opts usingComparator:(NSComparator)cmp to determine the insert position of a new object in a sorted array.
What is the best and high-performance way to do this in pure Swift?
Something along the lines of:
var myArray = ["b", "e", "d", "a"]
myArray.sort { $0 < $1 }
// myArray is now [a, b, d, e]
myArray.append("c")
myArray.sort { $0 < $1 }
// myArray is now [a, b, c, d, e]
Instead of appending the new element and then sorting the array, I would like to figure out the correct position and insert the element:
let index = [... how to calculate this index ??? ...]
myArray.insert("c", atIndex: index)
Here is a possible implementation in Swift using binary search (from
http://rosettacode.org/wiki/Binary_search#Swift with slight modifications):
extension Array {
func insertionIndexOf(_ elem: Element, isOrderedBefore: (Element, Element) -> Bool) -> Int {
var lo = 0
var hi = self.count - 1
while lo <= hi {
let mid = (lo + hi)/2
if isOrderedBefore(self[mid], elem) {
lo = mid + 1
} else if isOrderedBefore(elem, self[mid]) {
hi = mid - 1
} else {
return mid // found at position mid
}
}
return lo // not found, would be inserted at position lo
}
}
As with indexOfObject:inSortedRange:options:usingComparator: it is assumed that
the array is sorted with respect to the comparator.
It returns either (any) index of the element if the element is already present in the
array, or the index where it can be inserted while preserving the order. This
corresponds to the NSBinarySearchingInsertionIndex of the NSArray method.
Usage:
let newElement = "c"
let index = myArray.insertionIndexOf(newElement) { $0 < $1 } // Or: myArray.indexOf(c, <)
myArray.insert(newElement, at: index)
In swift 3 you can use index(where:):
var myArray = ["a", "b", "d", "e"]
let newElement = "c"
if let index = myArray.index(where: { $0 > newElement }) {
myArray.insert(newElement, at: index)
}
Note that in this case you need to reverse the condition inside the closure (i.e. > instead of < for increasing elements in array), because the index you are interested in is the first element that does NOT match the predicate. Also, this method will return nil if the newly inserted element is going to be the last in the array (newElement = "z" in the example above.
For convenience, this can be wrapped to a separate function that will handle all these issues:
extension Collection {
func insertionIndex(of element: Self.Iterator.Element,
using areInIncreasingOrder: (Self.Iterator.Element, Self.Iterator.Element) -> Bool) -> Index {
return index(where: { !areInIncreasingOrder($0, element) }) ?? endIndex
}
}
Usage:
var myArray = ["a", "b", "d", "e"]
let newElement = "c"
let index = myArray.insertionIndex(of: newElement, using: <)
myArray.insert(newElement, at: index)
According to WWDC 2018 Session 406: Swift Generics (Expanded) the binary search can be performed in a more efficient and even more generic way by slicing the collection object.
There are two considerable benefits of Slice:
A slice is always a subset of the original object without allocating additional memory.
All indices of the slice refer to the original object.
For example if you slice an array of 5 objects let slice = array[2..<4] then slice.startIndex is 2 not 0.
RandomAccessCollection is a protocol (inherited from BidirectionalCollection) which a variety of structs / classes conform to
extension RandomAccessCollection where Element : Comparable {
func insertionIndex(of value: Element) -> Index {
var slice : SubSequence = self[...]
while !slice.isEmpty {
let middle = slice.index(slice.startIndex, offsetBy: slice.count / 2)
if value < slice[middle] {
slice = slice[..<middle]
} else {
slice = slice[index(after: middle)...]
}
}
return slice.startIndex
}
}
Example:
let array = [1, 2, 4, 7, 8]
let index = array.insertionIndex(of: 6) // 3
You can extend the function to check against a predicate closure instead of a single value
extension RandomAccessCollection { // the predicate version is not required to conform to Comparable
func insertionIndex(for predicate: (Element) -> Bool) -> Index {
var slice : SubSequence = self[...]
while !slice.isEmpty {
let middle = slice.index(slice.startIndex, offsetBy: slice.count / 2)
if predicate(slice[middle]) {
slice = slice[index(after: middle)...]
} else {
slice = slice[..<middle]
}
}
return slice.startIndex
}
}
Example:
struct Person { let name : String }
let array = [Person(name: "Adam"), Person(name: "Cynthia"), Person(name: "Nancy"), Person(name: "Tom")]
let index = array.insertionIndex{ $0.name < "Bruce" } // 1
If you know your array is sorted, you can use this method -- it will work with arrays of any type. It will traverse the whole array each time, so don't use this with large arrays - go for another data type if you have larger needs!
func insertSorted<T: Comparable>(inout seq: [T], newItem item: T) {
let index = seq.reduce(0) { $1 < item ? $0 + 1 : $0 }
seq.insert(item, atIndex: index)
}
var arr = [2, 4, 6, 8]
insertSorted(&arr, newItem: 5)
insertSorted(&arr, newItem: 3)
insertSorted(&arr, newItem: -3)
insertSorted(&arr, newItem: 11)
// [-3, 2, 3, 4, 5, 6, 8, 11]
Building on #vadian's and #Martin R's answers, I noticed some minor discrepancies, mostly with the insertion index either not matching the index of an equivalent element in the collection, or of it not matching the first index of a sequence of equivalent elements.
For instance:
If you wanted to find an insertion index for 5 in [4, 5, 6], the index 2 would be returned, which may be problematic if you want to simply search for the value.
In [5, 5, 5], searching once again for 5 returns the index 1, which is not the first insertion index.
This doesn't match with the behavior of NSArray's implementation and its various options, so here is yet another solution that tries to take this into account:
extension RandomAccessCollection {
/// Get the index of or an insertion index for a new element in
/// a sorted collection in ascending order.
/// - Parameter value: The element to insert into the array.
/// - Returns: The index suitable for inserting the new element
/// into the array, or the first index of an existing element.
#inlinable
public func sortedInsertionIndex(
of element: Element
) -> Index where Element: Comparable {
sortedInsertionIndex(of: element, by: <)
}
/// Get the index of or an insertion index for a new element in
/// a sorted collection that matches the rule defined by the predicate.
/// - Parameters:
/// - value: The element to insert into the array.
/// - areInIncreasingOrder:
/// A closure that determines if the first element should
/// come before the second element. For instance: `<`.
/// - Returns: The index suitable for inserting the new element
/// into the array, or the first index of an existing element.
#inlinable
public func sortedInsertionIndex(
of element: Element,
by areInIncreasingOrder: (Element, Element) throws -> Bool
) rethrows -> Index {
try sortedInsertionIndex { try areInIncreasingOrder($0, element) }
}
/// Get the index of or an insertion index for a new element in
/// a sorted collection that matches the rule defined by the predicate.
///
/// This variation is useful when comparing an element that
/// is of a different type than those already in the array.
/// - Parameter isOrderedAfter:
/// Return `true` if the new element should come after the one
/// provided in the closure, or `false` otherwise. For instance
/// `{ $0 < newElement }` to sort elements in increasing order.
/// - Returns: The index suitable for inserting the new element into
/// the array, or the first index of an existing element.
#inlinable
public func sortedInsertionIndex(
where isOrderedAfter: (Element) throws -> Bool
) rethrows -> Index {
var slice: SubSequence = self[...]
while !slice.isEmpty {
let middle = slice.index(slice.startIndex, offsetBy: slice.count/2)
if try isOrderedAfter(slice[middle]) {
slice = slice[index(after: middle)...]
} else {
slice = slice[..<middle]
}
}
return slice.startIndex
}
}
Since sometimes you don't care about the insertion index, but instead the first or last index that matches a given element, here are variations on the above that satisfy those requirements as well:
extension RandomAccessCollection {
#inlinable
public func sortedFirstIndex(
of element: Element
) -> Index? where Element: Comparable {
sortedFirstIndex(of: element, by: <)
}
#inlinable
public func sortedFirstIndex(
of element: Element,
by areInIncreasingOrder: (Element, Element) throws -> Bool
) rethrows -> Index? where Element: Comparable {
let insertionIndex = try sortedInsertionIndex(of: element, by: areInIncreasingOrder)
guard insertionIndex < endIndex, self[insertionIndex] == element else { return nil }
return insertionIndex
}
#inlinable
public func sortedLastIndex(
of element: Element
) -> Index? where Element: Comparable {
sortedLastIndex(of: element, by: <)
}
#inlinable
public func sortedLastIndex(
of element: Element,
by areInIncreasingOrder: (Element, Element) throws -> Bool
) rethrows -> Index? where Element: Comparable {
let insertionIndex = try sortedInsertionIndex(of: element) { try areInIncreasingOrder($1, $0) }
let finalIndex = index(insertionIndex, offsetBy: -1)
guard finalIndex >= startIndex, self[finalIndex] == element else { return nil }
return finalIndex
}
}
A binary search tree here is the way to go.
On an ordered array, take the element in the middle and see if the object at that position is greater than your new object. That way you can forget the half of the array elements with one single comparison.
Repeat that step with the remaining half. Again, with a single comparison you can forget the half of the remaining objects. Your target element count is now a quarter of the array size at the beginning with only two comparisons.
Repeat that until you found the correct position to insert the new element.
Here is a a good article on binary search trees with swift
In swift 5:
var myArray = ["b", "e", "d", "a"]
myArray.sort { $0 < $1 }
// myArray is now [a, b, d, e]
let newElement = "c"
let index = myArray.firstIndex(where: { newElement < $0 })
myArray.insert(newElement, at: index ?? myArray.endIndex)
If you'd like to make the call-site more pretty:
extension Array where Element: Comparable {
/// Insert element in the correct location of a sorted array
mutating func insertSorted(_ element: Element) {
let index = firstIndex(where: { element < $0 })
insert(element, at: index ?? endIndex)
}
}
myArray.insertSorted("c")
Slight update from binary search:
extension Array {
mutating func binaryAppend(_ item:Element, sortBy: (Element,Element) -> Bool) {
var start:Int = 0
var end:Int = self.count - 1
while start <= end{
let mid = (start + end) / 2
if sortBy(self[mid], item){
start = start + 1
} else{
end = end - 1
}
}
self.insert(item, at: start)
}
}
You can use it like:
var arr = [1,3,5,7,9]
arr.binaryAppend(2, sortBy: {$0 < $1}) //[1,2,3,5,7,9]
Lets say I have an Array of String and I want to map it to an Array of Int
I can use the map function:
var arrayOfStrings: Array = ["0", "a"]
let numbersOptional = arrayOfStrings.map { $0.toInt() }
// numbersOptional = "[Optional(0), nil]"
Numbers is now an Array of Int?, but I want an Array of Int.
I know I can do this:
let numbers = arrayOfStrings.map { $0.toInt() }.filter { $0 != nil }.map { $0! }
// numbers = [0]
But that doesn't seem very swift. Converting from the Array of Int? to Array of Int requires calling both filter and map with pretty much boilerplate stuff. Is there a more swift way to do this?
Update: As of Swift 1.2, there's a built-in flatMap method for arrays, but it doesn't accept Optionals, so the helper below is still useful.
I like using a helper flatMap function for that sort of things, just like Scala's flatMap method on collections (which can consider an Scala Option as a collection of either 0 or 1 element, roughly spoken):
func flatMap<C : CollectionType, T>(source: C, transform: (C.Generator.Element) -> T?) -> [T] {
var buffer = [T]()
for elem in source {
if let mappedElem = transform(elem) {
buffer.append(mappedElem)
}
}
return buffer
}
let a = ["0", "a", "42"]
let b0 = map(a, { $0.toInt() }) // [Int?] - [{Some 0}, nil, {Some 42}]
let b1 = flatMap(a, { $0.toInt() }) // [Int] - [0, 42]
This definition of flatMap is rather a special case for Optional of what a more general flatMap should do:
func flatMap<C : CollectionType, T : CollectionType>(source: C, transform: (C.Generator.Element) -> T) -> [T.Generator.Element] {
var buffer = [T.Generator.Element]()
for elem in source {
buffer.extend(transform(elem))
}
return buffer
}
where we'd get
let b2 = flatMap(a, { [$0, $0, $0] }) // [String] - ["0", "0", "0", "a", "a", "a", "42", "42", "42"]
Using reduce to build the new array might be more idiomatic
func filterInt(a: Array<String>) -> Array<Int> {
return a.reduce(Array<Int>()) {
var a = $0
if let x = $1.toInt() {
a.append(x)
}
return a
}
}
Example
filterInt(["0", "a", "42"]) // [0, 42]
What you would really want is a collect (map + filter) method. Given the specific filter you need to apply, in this case even a flatMap would work (see Jean-Philippe's answer). Too bad both methods are not provided by the swift standard library.
update: Xcode 7.2 • Swift 2.1.1
let arrayOfStrings = ["0", "a", "1"]
let numbersOnly = arrayOfStrings.flatMap { Int($0) }
print(numbersOnly) // [0,1]
There’s no good builtin Swift standard library way to do this. However, Haskell has a function, mapMaybe, that does what you’re looking for (assuming you just want to ditch nil values). Here’s an equivalent in Swift:
func mapSome<S: SequenceType, D: ExtensibleCollectionType>
(source: S, transform: (S.Generator.Element)->D.Generator.Element?)
-> D {
var result = D()
for x in source {
if let y = transform(x) {
result.append(y)
}
}
return result
}
// version that defaults to returning an array if unspecified
func mapSome<S: SequenceType, T>
(source: S, transform: (S.Generator.Element)->T?) -> [T] {
return mapSome(source, transform)
}
let s = ["1","2","elephant"]
mapSome(s) { $0.toInt() } // [1,2]
You can consider using reduce, it's more flexible:
var arrayOfStrings: Array = ["0", "a"]
let numbersOptional = arrayOfStrings.reduce([Int]()) { acc, str in
if let i = str.toInt() {
return acc + [i]
}
return acc
}
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