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
I'm trying to update a math library to be compatible with Swift 3, but I'm running into an error:
'Sequence' requires the types 'T' and 'ArraySlice<T>' be equivalent
Apple's documentation on Sequence recommends that makeIterator() method returns an iterator, which it does. And it seems that the iterator is returning an element in the grid variable, which is of variable T. I'm not quite sure what I'm missing here. Any advice would be helpful.
public struct Matrix<T> where T: FloatingPoint, T: ExpressibleByFloatLiteral {
public typealias Element = T
let rows: Int
let columns: Int
var grid: [Element]
public init(rows: Int, columns: Int, repeatedValue: Element) {
self.rows = rows
self.columns = columns
self.grid = [Element](repeating: repeatedValue, count: rows * columns)
}
...
}
extension Matrix: Sequence { // <-- getting error here
public func makeIterator() -> AnyIterator<ArraySlice<Element>> {
let endIndex = rows * columns
var nextRowStartIndex = 0
return AnyIterator {
if nextRowStartIndex == endIndex {
return nil
}
let currentRowStartIndex = nextRowStartIndex
nextRowStartIndex += self.columns
return self.grid[currentRowStartIndex..<nextRowStartIndex]
}
}
}
Your code compiles fine as Swift 3.1 (Xcode 8.3.3). The error
'Sequence' requires the types 'T' and 'ArraySlice<T>' be equivalent
occurs when compiling as Swift 4 (Xcode 9, currently beta), because then
the Sequence protocol already defines the
associatedtype Element where Self.Element == Self.Iterator.Element
which conflicts with your definition. You can either choose a different
name for your type alias, or just remove it (and use T instead):
public struct Matrix<T> where T: FloatingPoint, T: ExpressibleByFloatLiteral {
let rows: Int
let columns: Int
var grid: [T]
public init(rows: Int, columns: Int, repeatedValue: T) {
self.rows = rows
self.columns = columns
self.grid = [T](repeating: repeatedValue, count: rows * columns)
}
}
extension Matrix: Sequence {
public func makeIterator() -> AnyIterator<ArraySlice<T>> {
let endIndex = rows * columns
var nextRowStartIndex = 0
return AnyIterator {
if nextRowStartIndex == endIndex {
return nil
}
let currentRowStartIndex = nextRowStartIndex
nextRowStartIndex += self.columns
return self.grid[currentRowStartIndex..<nextRowStartIndex]
}
}
}
This compiles and runs with both Swift 3 and 4.
I have created a struct called Contact which represents a human contact where there are currently a few in an Array. They are already sorted alphabetically however I would like to sort them alphabetically by the name property which is a String BUT I don't just want to have them in order in a single array, I would like to split the objects out into different collections which is corresponded by the first letter of their name. eg. "A" contains 2 objects where a Contacts name begins with A, "B" for names like Bobby, Brad etc.. and so on and so forth.
let contactData:[Contact] = [
Contact(id: 1, available: true, name: "Adam"),
Contact(id: 2, available: true, name: "Adrian"),
Contact(id: 3, available: true, name: "Balthazar"),
Contact(id: 4, available: true, name: "Bobby")
]
I would like to create something like
let sectionTitles = ["A", "B"]
let sortedContactData = [
[
Contact(name: "Adam"),
Contact(name: "Adrian")
],
[
Contact(name:"Balthazar")
Contact(name:"Bobby")
]
]
Or something similar...
The end result is that I would like to display them into a UITableView with the letters in Sections and the Objects into indexPath.rows much like how the Contacts app native to the iPhone does it. I am actually not sure whether this is the most ideal way to achieve this result so I welcome any challenges to this question!
let sortedContacts = contactData.sorted(by: { $0.name < $1.name }) // sort the Array first.
print(sortedContacts)
let groupedContacts = sortedContacts.reduce([[Contact]]()) {
guard var last = $0.last else { return [[$1]] }
var collection = $0
if last.first!.name.characters.first == $1.name.characters.first {
last += [$1]
collection[collection.count - 1] = last
} else {
collection += [[$1]]
}
return collection
}
print(groupedContacts)
sort the list. O(nlogn) , where n is the number of items in the Array(contactData).
use reduce to iterate each contact
in the list, then either add it to new group, or the last one. O(n), where n is the number of items in the Array(sortedContacts).
If you need to have a better printed information, you better make Contact conforms to protocol CustomStringConvertible
Chunk up a collection based on a predicate
We could let ourselves be inspired by Github user oisdk:s chunk(n:) method of collection, and modify this to chunk up a Collection instance based on a supplied (Element, Element) -> Bool predicate, used to decide whether a given element should be included in the same chunk as the preceeding one.
extension Collection {
func chunk(by predicate: #escaping (Iterator.Element, Iterator.Element) -> Bool) -> [SubSequence] {
var res: [SubSequence] = []
var i = startIndex
var k: Index
while i != endIndex {
k = endIndex
var j = index(after: i)
while j != endIndex {
if !predicate(self[i], self[j]) {
k = j
break
}
formIndex(after: &j)
}
res.append(self[i..<k])
i = k
}
return res
}
}
Applying this to your example
Example setup (where we, as you've stated, assume that the contactData array is already sorted).
struct Contact {
let id: Int
var available: Bool
let name: String
}
let contactData: [Contact] = [
Contact(id: 1, available: true, name: "Adam"),
Contact(id: 2, available: true, name: "Adrian"),
Contact(id: 3, available: true, name: "Balthazar"),
Contact(id: 4, available: true, name: "Bobby")
]
Using the chunk(by:) method above to split the contactData array into chunks of Contact instances, based on the initial letter of their names:
let groupedContactData = contactData.chunk {
$0.name.characters.first.map { String($0) } ?? "" ==
$1.name.characters.first.map { String($0) } ?? ""
}
for group in groupedContactData {
print(group.map { $0.name })
} /* ["Adam", "Adrian"]
["Balthazar", "Bobby"] */
Improving the chunk(by:) method above
In my initial (non-compiling) version of chunk(by:) above, I wanted to make use of the index(where:) method available to Slice instances:
// does not compile!
extension Collection {
func chunk(by predicate: #escaping (Iterator.Element, Iterator.Element) -> Bool) -> [SubSequence] {
var res: [SubSequence] = []
var i = startIndex
var j = index(after: i)
while i != endIndex {
j = self[j..<endIndex]
.index(where: { !predicate(self[i], $0) } ) ?? endIndex
/* ^^^^^ error: incorrect argument label in call
(have 'where:', expected 'after:') */
res.append(self[i..<j])
i = j
}
return res
}
}
But it seems as if it can not resolve this method correctly, probably due to a lacking constraint (Collection where ...) in the extension. Maybe someone can shed light on how to allow the stdlib-simplified extension above?
We may, however, implement this somewhat briefer extension if we apply it to Array, in which case index(where:) can be successfully called on the ArraySlice instance (self[...]):
// ok
extension Array {
func chunk(by predicate: #escaping (Iterator.Element, Iterator.Element) -> Bool) -> [SubSequence] {
var res: [SubSequence] = []
var i = startIndex
var j = index(after: i)
while i != endIndex {
j = self[j..<endIndex]
.index(where: { !predicate(self[i], $0) } ) ?? endIndex
res.append(self[i..<j])
i = j
}
return res
}
}
IMHO there is no single-map way to do that, so the algorithm is:
var sectionedData: [String: [Contact]] = [:]
contactData.forEach {
guard let firstLetter = $0.name.characters.first else {
sectionedData["#"] = (sectionedData["#"] ?? []) + [$0]
return
}
let firstLetterStr = String(firstLetter)
sectionedData[firstLetterStr] = (sectionedData[firstLetterStr] ?? []) + [$0]
}
let sortedContactData = sectionedData.sorted(by: { $0.0.key < $0.1.key })
A solution for filtering and splitting a collection to smaller collections as many as given predicates.
e.g. given array of ints [1, 2, 3, 4] applying predicates: odd, even and >3,
result would be [ [1, 3], [2, 4], [4] ]
Note: The subsets can be repetitive depending on the given predicates. (I'm curious if this can be improved. reduce has complexity: O(n))
extension Collection {
func filterParts(_ predicates: ((Element) -> Bool)...) -> [ [Element] ] {
let empty = predicates.map { _ -> [Element] in return [] }
let enumeratedPredicates = predicates.enumerated()
return reduce(empty) { (result, element) in
var result = result
enumeratedPredicates.forEach { offset, predicate in
if predicate(element) {
result[offset].append(element)
}
}
return result
}
}
}
Use Dictionary's init(grouping:by:) like so:
lazy var sectionDictionary: Dictionary<String, [Contact]> = {
return Dictionary(grouping: contactData, by: {
// assumes title is a non-empty string
let name = $0.name
let normalizedName = name.folding(options: [.diacriticInsensitive, .caseInsensitive], locale: .current)
let firstCharAsString = String(normalizedName.first!).uppercased()
return firstCharAsString
})
}()
I spelled out the different transformation steps but you can combine them in a one-liner if you like.
This yields a dictionary with the section names as keys and arrays of objects as values.
From there you can easily extract the section array of arrays and you get your section names array for free:
lazy var sectionTitles: [String] = {
return self.sectionDictionary.keys.sorted()
}()
lazy var sections: Array<[String]> = {
return self.sectionTitles.map { sectionDictionary[$0]! }
}()
Please mind that I use some force unwrapping which you should guard from in production code.
I am learning iOS development, and have recently come across an issue when attempting to manipulate an array of tuples.
I get the following error message:
Cannot subscript a value of type '[(String, Int)]' with an index of type '(String, Int)'
The code generating it is as follows:
justStrings.append(filteredRestraunts[i2].0)
The function as a whole is this:
func filterBySliderValue () -> [String] {
var filteredRestraunts: [(String, Int)]
for var i = 0; i < restraunts.count; i++ {
if restraunts[i].1 > Int(starSlider.value) {
filteredRestraunts.append(restraunts[i])
}
else {filteredRestraunts.append(("", 1))}
}
var justStrings: [String]
var i2 = 0
for i2 in filteredRestraunts {
justStrings.append(filteredRestraunts[i2].0)
}
return justStrings
}
This is the array restraunts:
var restraunts: [(String, Int)] = [("Dallas BBQ", 3), ("Chicken Express", 4), ("Starbucks", 5)]
Thanks in advance.
In
for i2 in filteredRestraunts {
justStrings.append(filteredRestraunts[i2].0)
}
i2 is not an index, but iterates over the array elements, i.e.
it is a (String, Int) tuple. What you probably meant is
for i2 in filteredRestraunts {
justStrings.append(i2.0)
}
Additional remarks:
The variable
var i2 = 0
is not used at all, i2 in the for-loop is a new variable whose scope is
restricted to the loop.
The variables filteredRestraunts and justStrings
are not initialized, so this should cause additional compiler errors.
Both loops can be replaced by a more functional approach using
filter and map:
let filteredRestraunts = restraunts.filter { $0.1 > Int(starSlider.value) }
let justStrings = filteredRestraunts.map { $0.0 }
Which of course could be combined to
let justStrings = restraunts.filter { $0.1 > Int(starSlider.value) }.map { $0.0 }
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