How to alphabetize an array of objects within Swift 3? - ios

I try to list an array of objects in alphabetic order. I create this simple test, but doesn't know how to achieve this with an array of objects:
let names = ["Martin", "Nick", "Alex", "Ewa", "Barry", "Daniella", "Chris", "Robert", "Andrew"]
func alphabetizeArray(_ s1: String, _ s2: String) -> Bool {
return s1 < s2
}
let alphabeticNames = names.sorted(by: names)
print(reversedNames)
When I try this for an array of objects I came up with something like this:
func sorterForIDASC(this:People, that:People) -> Bool {
return this.name > that.name
}
peoples.sort(by: sorterForIDASC)
But this will give me an error of: Binary operator '>' cannot be applied to two 'String?' operands
Anyone suggestions how to solve this. I would examine the names of each object that is from the type of String. I use Swift 3/Xcode8.

If you only need > then implementing > for your optional property is sufficient:
func >(lhs: People, rhs: People) -> Bool {
if let left = lhs.name, let right = rhs.name {
return left > right
}
return false
}
Now you can use > on an array of your objects:
let result = arrayOfObjects.sorted(by: >)
You could also have your object conform to Equatable and implement at least == and > for the optional property:
struct People: Equatable {
var name: String?
}
func ==(lhs: People, rhs: People) -> Bool {
if let left = lhs.name, let right = rhs.name {
return left == right
}
return false
}
func >(lhs: People, rhs: People) -> Bool {
if let left = lhs.name, let right = rhs.name {
return left > right
}
return false
}
This opens even more possibilities.

In Swift 3.0
you can do that simply like this.
peoples.sort(by: { (this: People, that: People) -> Bool in
this. name < that. name
})

Related

Safe and elegant way to delete element in custom array, Swift [duplicate]

This question already has answers here:
Swift: Better way to remove a specific Object from an array?
(5 answers)
Closed 2 years ago.
I have this custom array:
var customArray = [CustomItem]()
Custom item is:
class CustomItem {
let firstItem: Enumeration
let data: Any
// ...
}
Enumeration contains an enumaration with different case. For example case example1, case example2 etc.
I add element with append with all the info like firstItem (enumeration etc).
What I need is to check if in my customArray I have a given item in my enumeration. Let's say I have to check if in customArray the enumeration .example1 exist (because I append it before) and in case delete it.
What is the safest and most elegant way to perform this?
Extra: what If i would like to add one element at the end of this custom arrray?
if you want find a specific item index you can use firstIndex(of: )
like this:
let index = customArray.firstIndex(of: CustomItem)
customArray.remove(at: index)
EDIT:
You have to make your object Equatable with an extension:
enter codextension CustomItem: Equatable {
static func ==(lhs: CustomItem, rhs: CustomItem) -> Bool {
return lhs.firstItem == rhs.firstItem
} }
Now you can compare the objects with each other.
this is my code from playground:
enum Enumeration {
case opt1
case op2
}
struct CustomItem {
var firstItem: Enumeration
let data: Any
}
extension CustomItem: Equatable {
static func ==(lhs: CustomItem, rhs: CustomItem) -> Bool {
return lhs.firstItem == rhs.firstItem
}
}
class ViewController: UIViewController {
var customArray = [CustomItem]()
func searchAndDestory() {
let index = customArray.firstIndex(of: CustomItem.init(firstItem: .op2, data: 0)) ?? 0
customArray.remove(at: index)
}
}

Subclassing MKAnnotation cause Set collection doesn’t work

I've just found that Set of type MKAnnotation doesn't work as expected.
class MyAnnotation: MKPointAnnotation {
let id: String
init(_ id: String) {
self.id = id
}
override var hash: Int {
return id.hash
}
static func ==(lhs: MyAnnotation, rhs: MyAnnotation) -> Bool {
return lhs.hashValue == rhs.hashValue
}
}
let m1 = MyAnnotation("1")
let m2 = MyAnnotation("2")
let n1 = MyAnnotation("1")
let n2 = MyAnnotation("2")
m1.hashValue //918
n1.hashValue //918
m2.hashValue //921
n2.hashValue //921
if m1 == n1 && m2 == n2 {
print(true)
}
// prints true
let s1 = Set(arrayLiteral: m1, m2)
let s2 = Set(arrayLiteral: n1, n2)
let i = s1.intersection(s2)
// empty
Intersection of m's and n's is empty even so hashes are the same. Please, compare with example below:
class MyAnnotation: Hashable, Equatable {
let id: String
init(_ id: String) {
self.id = id
}
var hashValue: Int {
return id.hash
}
static func ==(lhs: MyAnnotation, rhs: MyAnnotation) -> Bool {
return lhs.hashValue == rhs.hashValue
}
}
let m1 = MyAnnotation("1")
let m2 = MyAnnotation("2")
let n1 = MyAnnotation("1")
let n2 = MyAnnotation("2")
m1.hashValue //918
n1.hashValue //918
m2.hashValue //921
n2.hashValue //921
if m1 == n1 && m2 == n2 {
print(true)
}
// prints true
let s1 = Set(arrayLiteral: m1, m2)
let s2 = Set(arrayLiteral: n1, n2)
let i = s1.intersection(s2)
// {{id "1"}, {id "2"}}
Intersection of m's and n's is as expected.
Isn't it weird? Maybe there is something in the middle I don't know nor understand.
Xcode 10.1
In first code, you haven't use Equatable protocol but in second code you have used Equatable protocol so it is working.
Equatable protocol is used for comparison. You can refer below link for more information:
https://useyourloaf.com/blog/swift-equatable-and-comparable/
The solution is to override isEqual(_ object: Any?) -> Bool.
Because MKAnnotation is derived from NSObject we need to override NS's method. If we dig into implementation (^ + ⌘) we would find:
Subclasses of NSObject can customize Equatable conformance by overriding isEqual(_:). If two objects are equal, they must have the same hash value, so if you override isEqual(_:), make sure you also override the hash property.

~= operator in Swift

I recently downloaded the Advanced NSOperations sample app from Apple and found this code...
// Operators to use in the switch statement.
private func ~=(lhs: (String, Int, String?), rhs: (String, Int, String?)) -> Bool {
return lhs.0 ~= rhs.0 && lhs.1 ~= rhs.1 && lhs.2 == rhs.2
}
private func ~=(lhs: (String, OperationErrorCode, String), rhs: (String, Int, String?)) -> Bool {
return lhs.0 ~= rhs.0 && lhs.1.rawValue ~= rhs.1 && lhs.2 == rhs.2
}
It seems to use the ~= operator against Strings and Ints but I've never seen it before.
What is it?
Simply use as a shortcut to "range": you can construct a range and "~=" means "contains".
(other can add more theoretical details, but the sense is this). Read it as "contains"
let n: Int = 100
// verify if n is in a range, say: 10 to 100 (included)
if n>=10 && n<=100 {
print("inside!")
}
// using "patterns"
if 10...100 ~= n {
print("inside! (using patterns)")
}
try with some values of n.
Is used widely for example in HTTP response:
if let response = response as? HTTPURLResponse , 200...299 ~= response.statusCode {
let contentLength : Int64 = response.expectedContentLength
completionHandler(contentLength)
} else {
completionHandler(nil)
It is an operator used for pattern matching in a case statement.
You can take a look here to know how you can use and leverage it providing your own implementation:
http://oleb.net/blog/2015/09/swift-pattern-matching/
http://austinzheng.com/2014/12/17/custom-pattern-matching/
Here is a simple example of defining a custom one and using it:
struct Person {
let name : String
}
// Function that should return true if value matches against pattern
func ~=(pattern: String, value: Person) -> Bool {
return value.name == pattern
}
let p = Person(name: "Alessandro")
switch p {
// This will call our custom ~= implementation, all done through type inference
case "Alessandro":
print("Hey it's me!")
default:
print("Not me")
}
// Output: "Hey it's me!"
if case "Alessandro" = p {
print("It's still me!")
}
// Output: "It's still me!"
You can look into Define Swift
func ~=<I : IntervalType>(pattern: I, value: I.Bound) -> Bool
func ~=<T>(lhs: _OptionalNilComparisonType, rhs: T?) -> Bool
func ~=<T : Equatable>(a: T, b: T) -> Bool
func ~=<I : ForwardIndexType where I : Comparable>(pattern: Range<I>, value: I) -> Bool

Sort an array based on the second value of a tuple

thanks for reading my post.
I have an array of tuples declared as such:
var myArray: [(item1: String?, item2: NSDate?)] = []
At the end of my loop I want to sort my array of tuples based on every tuple's item2, whose type is NSDate?.
Based on this answer and this answer I tried the following, but received this compiler error, "cannot invoke 'sort' with an argument list of type '((_,_) -> _)'.
Here is what I tried:
myArray.sort {$0.1.item2?.compare($1.1.item2?) == NSComparisonResult.OrderedDescending }
P.S. println() works fine and prints item1 and item2 as an optional.
You must implement Comparable protocol to NSDate
public func ==(lhs: NSDate, rhs: NSDate) -> Bool {
return lhs === rhs || lhs.compare(rhs) == .OrderedSame
}
public func <(lhs: NSDate, rhs: NSDate) -> Bool {
return lhs.compare(rhs) == .OrderedAscending
}
extension NSDate: Comparable { }
After that you can sort your tuples by date:
myArray!.sort {$0.1 == $1.1 ? $0.1 > $1.1 : $0.1 > $1.1 }
An alternative to the accepted solution:
let res = myArray.sort { (left, right) -> Bool in
return left.item2?.timeIntervalSinceReferenceDate < right.item2?.timeIntervalSinceReferenceDate
}
The reason why the linked solutions did not work is because the array of tuples defined in the question contains optional types.
Checking for the optionals fixes the problem, without having to add new operators to NSDate.
An example, with 3 dates, and optional types:
var myArray: [(item1: String?, item2: NSDate?)] = []
myArray = [("now", NSDate()), ("now+30s", NSDate().dateByAddingTimeInterval(NSTimeInterval(30))), ("now-30s", NSDate().dateByAddingTimeInterval(NSTimeInterval(-30)))]
myArray.sortInPlace { (lhs, rhs) -> Bool in
if lhs.item2 != nil && rhs.item2 != nil {
return lhs.item2!.compare(rhs.item2!) == .OrderedAscending
}
return false // Return true if you want nil values first
}
Same code, if the types didn't allow for optionals:
var myArray: [(item1: String, item2: NSDate)] = []
myArray = [("now", NSDate()), ("now+30s", NSDate().dateByAddingTimeInterval(NSTimeInterval(30))), ("now-30s", NSDate().dateByAddingTimeInterval(NSTimeInterval(-30)))]
myArray.sortInPlace { (lhs, rhs) -> Bool in
return lhs.item2.compare(rhs.item2) == .OrderedAscending
}
MirekE's solution also works well, but you do not have control on where the nil values would end (they will be at the beginning).

How to determine if one array contains all elements of another array in Swift?

I have 2 arrays:
var list:Array<Int> = [1,2,3,4,5]
var findList:Array<Int> = [1,3,5]
I want to determine if list Array contains all findList elements.
By the way, elements might be String as well or other type.
How to do that?
I know that Swift provides contains method that works with one item.
Instead of iterating through arrays and doing filtering yourself, you can use NSSet to do all the work for you.
var list:Array<Int> = [1,2,3,4,5]
var findList:Array<Int> = [1,3,5]
let listSet = NSSet(array: list)
let findListSet = NSSet(array: findList)
let allElemtsEqual = findListSet.isSubsetOfSet(otherSet: listSet)
NSSet is a lot faster than arrays at checking if it contains any object. In fact it's what it's designed for.
Edit: Using Swift's built-in Set.
let list = [1,2,3,4,5]
let findList = [1,3,5]
let listSet = Set(list)
let findListSet = Set(findList)
//**Swift 4.2 and Above**
let allElemsContained = findListSet.isSubset(of: listSet)
//below versions
//let allElemsContained = findListSet.isSubsetOf(listSet)
allSatisfy seems to be what you want, assuming you can't conform your elements to Hashable and use the set intersection approach others have mentioned:
let containsAll = subArray.allSatisfy(largerArray.contains)
Since Swift 4.2 you can write:
extension Array where Element: Equatable {
func satisfy(array: [Element]) -> Bool {
return self.allSatisfy(array.contains)
}
}
Otherwise for Swift 3, Swift 4 you can write this:
extension Array where Element: Equatable {
func contains(array: [Element]) -> Bool {
for item in array {
if !self.contains(item) { return false }
}
return true
}
}
You can see the:
contains method here
allSatisfy method here
This is just a simple extension that check if the array that you give is in the current array (self)
You can use the filter method to return all elements of findList which are not in list:
let notFoundList = findList.filter( { contains(list, $0) == false } )
then check if the length of the returned array is zero:
let contained = notFoundList.count == 0
Note that his solution traverses the entire findList array, so it doesn't stop as soon as a non contained element is found. It should be used if you also want to know which elements are not contained.
If you just need a boolean stating whether all elements are contained or not, then the solution provided by Maxim Shoustin is more efficient.
Consider following generic method:
func arrayContainsArray<S : SequenceType where S.Generator.Element : Equatable>
(src:S, lookFor:S) -> Bool{
for v:S.Generator.Element in lookFor{
if contains(src, v) == false{
return false
}
}
return true
}
The advantage - method stops after 1st fail and do not continue over findList
Tests
var listAsInt:Array<Int> = [1,2,3,4,5]
var findListAsInt:Array<Int> = [1,3,5]
var result = arrayContainsArray(listAsInt, findListAsInt) // true
listAsInt:Array<Int> = [1,2,3,4,5]
findListAsInt:Array<Int> = [1,3,5,7,8,9]
result = arrayContainsArray(listAsInt, findListAsInt) // false
var listOfStr:Array<String> = ["aaa","bbb","ccc","ddd","eee"]
var findListOfStr:Array<String> = ["bbb","ccc","eee"]
result = arrayContainsArray(listOfStr, findListOfStr) // true
listOfStr:Array<String> = ["aaa","bbb","ccc","ddd","eee"]
findListOfStr:Array<String> = ["bbb","ccc","eee","sss","fff","ggg"]
result = arrayContainsArray(listOfStr, findListOfStr) // false
(tested on Beta7)
As a complement to Sequence.contains(element) handling multiple elements, add this extension:
public extension Sequence where Element : Hashable {
func contains(_ elements: [Element]) -> Bool {
return Set(elements).isSubset(of:Set(self))
}
}
Used:
list.contains(findList)
Since this uses Set/Hashable it performs much better than Equatable alternatives.
Right now, I'd probably use something like:
let result = list.reduce(true, { $0 ? contains(findList, $1) : $0 })
...but then I did just read this article, which might be biasing me towards this kind of solution. You could probably make this more efficient without making it completely unreadable, but it's early and I've not had my coffee.
Extend the Array with the following methods:
extension Array {
func contains<T where T : Equatable>(obj: T) -> Bool {
return self.filter({$0 as? T == obj}).count > 0
}
func isEqualTo< T : Equatable> (comparingArray : [T]) -> Bool {
if self.count != comparingArray.count {
return false
}
for e in comparingArray {
if !self.contains(e){
return false
}
}
return true
}
}
An example of how you can use it like this:
if selectedDates.isEqualTo(originalDates) {
//Arrays the same hide save button
} else {
//Arrays not the same, show Save & Discard Changes Button (if not shown)
}
Shout out to #David Berry for the contain method.
None of the previous answers seem to be right.
consider:
let a = [2,2]
let b = [1,2,3]
we wouldn't say that b actually "contains" a, but if your algorithm is based on for-loop & swift's built-in contains(element:) or a set, the above case would pass.
I use this set of extended methods myself. I hope this code snippet helps:
// Array + CommonElements.swift
import Foundation
public extension Array where Element: Hashable {
func set() -> Set<Array.Element> {
return Set(self)
}
func isSubset(of array: Array) -> Bool {
self.set().isSubset(of: array.set())
}
func isSuperset(of array: Array) -> Bool {
self.set().isSuperset(of: array.set())
}
func commonElements(between array: Array) -> Array {
let intersection = self.set().intersection(array.set())
return intersection.map({ $0 })
}
func hasCommonElements(with array: Array) -> Bool {
return self.commonElements(between: array).count >= 1 ? true : false
}
}
This is Maxim Shoustin's answer updated for Swift 3:
func arrayContainsArray<S : Sequence>
(src:S, lookFor:S) -> Bool where S.Iterator.Element : Equatable{
for v:S.Iterator.Element in lookFor{
if src.contains(v) == false{
return false
}
}
return true
}
If you need to determine, that one array is subArray of another.
public extension Array where Element: Equatable {
func isSuperArray(of array: Array<Element>) -> Bool {
guard
count >= array.count,
let indexes = array.first.flatMap(indexes(of:)),
!indexes.isEmpty else {
return false
}
let arraysForComparison = indexes
.compactMap { index -> [Element]? in
guard index + (array.count - 1) <= count else { return nil }
return Array(self[index..<(index + array.count)])
}
return arraysForComparison.contains(array)
}
func isSubArray(of array: Array<Element>) -> Bool {
array.isSuperArray(of: self)
}
private func indexes(of element: Element) -> [Index] {
enumerated()
.filter { element == $0.1 }
.map { index, _ in index }
}
}
Example of usage:
let array1 = [1, 2, 3, 4]
let array2 = [2, 3]
print(array1.isSuperArray(of: array2)) // true
print(array2.isSubArray(of: array1)) // true
print(array2.isSuperArray(of: array1)) // false
print(array1.isSubArray(of: array2)) // false

Resources