swift generics return first and last element - swift

I'm trying to get used to generics (never used them in objc) and want to write a toy function that takes an object of any type () and returns the first and last element. Hypothetically, I'd only use this on an array or a string - I keep getting an error that has no subscript members. I totally understand that the error message is telling me swift has no clue that T may potentially hold a type that does have subscripts - I just want to know how to get around this.
func firstAndLastFromCollection<T>(a:T?) {
var count: Int = 0
for item in a as! [AnyObject] {
count++
}
if count>1 {
var first = a?[0]
var last = a?[count-1]
return (first, last)
}
return something else here
}
Do I need to typecast somewhere here (which would kind of defeat the purpose here, as I'd need to downcast as either a string or an array, adding code and lessening how generic this func is)?

If you want to return the first and the last element then it's probably safe assuming the input param is an array of some kind of type.
So you can implement your function this way
func firstAndLast<T>(list:[T]) -> (first:T, last:T)? {
guard let first = list.first, last = list.last else { return nil }
return (first, last)
}
The function does return a tuple of 2 element, both have the same type of the generic element of the input array.
The returned tuple is an option because if the array is empty then nil is returned.
Examples
let nums = firstAndLast([1,2,3,4])
let words = firstAndLast(["One", "Two", "Three"])
As you can verify the type of the generic element into the array becomes the type of the elements inside the tuple.
In the example above nums is inferred to be (Int, Int)? and words (Words, Words)?
More examples
let emptyList: [String] = []
firstAndLast(emptyList) // nil
Extension
Finally you can also write this code as an extension of Array.
extension Array {
var firstAndLast: (first:Element, last:Element)? {
guard let first = self.first, last = self.last else { return nil }
return (first, last)
}
}
Now you can write
let aCoupleOfShows = ["Breaking Bad", "Better Call Saul", "Mr Robot"].firstAndLast
Again, if you check the type of the constant aCoupleOfShows you'll see that is a (first: String, last: String)?. Swift automatically did infer the correct type.
Last example
In the comments you said you wanted the first and last chars of a String. here it is the code if you use the extension above
if let chars = Array("Hello world".characters).firstAndLast {
print("First char is \(chars.first), last char is \(chars.last) ")
}
//>> First char is H, last char is d

If we are talking about collections, let's use the CollectionType:
func firstAndLastFromCollection<T: CollectionType>(a: T) -> (T.Generator.Element, T.Generator.Element)? {
guard !a.isEmpty else {
return nil
}
return (a.first!, a.lazy.reverse().first!)
}
print(firstAndLastFromCollection(["a", "b", "c"])) // ("a", "c")
print(firstAndLastFromCollection("abc".characters)) // ("a", "c")
print(firstAndLastFromCollection(0..<200)) // (0, 199)
print(firstAndLastFromCollection([] as [String])) // nil
If you specify your generic type to also conform to bidirectional index:
func firstAndLastFromCollection<T: CollectionType where T.Index : BidirectionalIndexType>(...) -> ...
then you can call last directly:
return (a.first!, a.last!)
If we decide to implement it using a category, we don't need generics at all:
extension CollectionType {
func firstAndLast() -> (Generator.Element, Generator.Element)? {
guard !self.isEmpty else {
return nil
}
return (self.first!, self.lazy.reverse().first!)
}
}
extension CollectionType where Index: BidirectionalIndexType {
func firstAndLast() -> (Generator.Element, Generator.Element)? {
guard !self.isEmpty else {
return nil
}
return (self.first!, self.last!)
}
}
print("abc".characters.firstAndLast())
Swift is a protocol oriented language. Usually you will find yourself extend protocols more than extending classes or structs.

Related

Swift - How to use a class type as a parameter / variable

I'm trying to create a somewhat generic function to sort file URLs based the value of an attribute
My goal is to:
1) Pass the URL and some parameters (Including the type) to a function.
which will then loop through the file's attributes
2) Add the matching file attribute and the URL to an array of tuples
3) Sort the tuples by the value of the found attribute
4) Return the sorted array and display the items in sorted order
I believe that I need to pass the type of the attribute into the sorting function so I'm able to set it in the tuple since I'm unable to sort with "Any" but I'm unsure of how to do that
I'm okay with passing anything into the sorting function and constructing or deconstructing the value I need in the sorting function since that will be predefined depending upon what action is selected by the user
//Initial implementation to be later tied to IBActions and simplified
func sortFiles(sortByKeyString : String, fileURLArray : [URL]) -> [URL]
{
switch sortByKeyString {
case "date-created-DESC":
let fileAttributeKeyString : String = "creationDate"
let isSortOrderDesc = true
let objectTypeString : String = NSDate.className()
let sortedFileURLArray = sortFileArrayByType(fileAttributeKeyString: fileAttributeKeyString, fileURLArray: fileURLArray, type: objectTypeString, isSortOrderDesc : isSortOrderDesc)
return sortedFileURLArray
default:
return fileURLArray
}
}
//Generic function to get a files attributes from a URL by requested
type
func sortFileArrayByType(fileAttributeKeyString : String, fileURLArray : [URL], type: String, isSortOrderDesc : Bool) -> [URL] {
let fileManager = FileManager.default
let attributeToLookFor : FileAttributeKey = FileAttributeKey.init(rawValue: fileAttributeKeyString)
var tupleArrayWithURLandAttribute : [(url: URL, attribute: *Any*)]? = nil
for url in fileURLArray {
do {
let attributes = try fileManager.attributesOfItem(atPath: url.path)
for (key, value) in attributes {
if key.rawValue == fileAttributeKeyString {
tupleArrayWithURLandAttribute?.append((url: url, attribute: value))
}
}
let sortedTupleArrayWithURLandAttribute = tupleArrayWithURLandAttribute?.sorted(by: { $0.attribute < $1.attribute)})
// Need to Sort dictionary into array
return sortedTupleArrayWithURLandAttribute
} catch {
return fileURLArray
}
}
}
First read Metatype Type in the The Swift Programming Language. Once read continue with the answer.
From that you have learnt that you can declare a function parameter's type to be the type of types (you are allowed to go crosseyed), AKA metatype, and can therefore pass a type to a function. Combine that with generics and Swift's type inference and you could declare your function as:
func sortFileArrayByType<T>(fileAttributeKeyString : String,
attributeType : T.Type,
fileURLArray : [URL]
) -> [(url: URL, attribute: T)]
where T : Comparable
This adds the parameter attributeType whose type is the metatype of T where T will be inferred. For example the metatype String.self could be passed and T will be inferred to be String.
The where clause constrains T so that only types which are Comparable are allowed, this is required to enable the function to do sorting. File attributes can be Date, String and NSNumber valued; unfortunately the latter does not conform to Comparable so you need to add an extension to make it, the following will suffice:
extension NSNumber : Comparable
{
public static func <(a : NSNumber, b : NSNumber) -> Bool { return a.compare(b) == .orderedAscending }
public static func ==(a : NSNumber, b : NSNumber) -> Bool { return a.compare(b) == .orderedSame }
}
Within the body of the function you need to declare your array of tuples to have attributes of type T:
var tupleArrayWithURLandAttribute : [(url: URL, attribute: T)] = []
and when you add entries you need to cast the value returned by attributesOfItem to be T:
tupleArrayWithURLandAttribute.append((url: url, attribute: value as! T))
Note the use of as! here, you must match the attribute name and the type of its value correctly in the function call or you will get a runtime abort. Handling this as a soft error, if needed, is left as an exercise.
There are a number of typos etc. in the code you posted, they are left for you to fix, having done that your function should work. A call might look like:
let ans = sortFileArrayByType2(fileAttributeKeyString: "NSFileCreationDate",
attributeType: Date.self,
fileURLArray: urlArray)
and the type of ans in this case will be [(url: URL, attribute: Date)]
HTH
So I think I know what you're getting at and this is what I've come up with:
func checkType<T>(_ type: T.Type) {
if type.self == String.self {
print("It's a string!")
} else if type.self == Int.self {
print("It's an int!")
} else {
print("It's something else...")
}
}
And then you can call this either by passing in a type directly to it, or by getting the type of a variable and passing that in as follows:
checkType(String.self) // prints "It's a string!"
let number: Int = 1
checkType(type(of: number)) // prints "It's an int!"
Hope this helps!
What you're looking for here is a way to sort a sequence of URLs by a URLResourceKey (and specifically by the URLResourceValues property related to that key). Unfortunately, URLResourceValues aren't mapped to URLResourceKey in a useful way. But we can fix that with an extension:
extension URLResourceValues {
static func key<T>(for keyPath: KeyPath<Self, T>) -> URLResourceKey {
switch keyPath {
case \Self.creationDate: return .creationDateKey
// ... Other keys ...
default: fatalError()
}
}
}
And it would be very useful to get a value for a URLResourceValues keyPath:
extension URL {
func resourceValue<T>(for keyPath: KeyPath<URLResourceValues, T?>) throws -> T? {
return try resourceValues(forKeys: Set([URLResourceValues.key(for: keyPath)]))[keyPath: keyPath]
}
}
With that, we can build a sorting method based on URLResourceValues (assuming nil is less than other values; you could replace that with throwing for non-existent values):
extension Sequence where Element == URL {
func sorted<T>(by keyPath: KeyPath<URLResourceValues, T?>) throws -> [URL]
where ResourceType: Comparable {
return try self
.sorted { (lhs, rhs) in
guard let lhsValue = try lhs.resourceValue(for: keyPath)
else { return true }
guard let rhsValue = try rhs.resourceValue(for: keyPath)
else { return false }
return lhsValue < rhsValue
}
}
}
And finally, that can be used by passing a keypath, based on URLResourceValues:
let sortedFiles = try files.sorted(by: \.creationDate)

Swift testing non-scalar types

I want to test my function that takes a string, a returns all the pairs of characters as an array s.t.
func pairsOfChars(_ s: String) -> [(Character,Character)] {
let strArray = Array(s)
var outputArray = [(Character,Character)]()
for i in 0..<strArray.count - 1 {
for j in i + 1..<strArray.count {
outputArray.append( (strArray[i], strArray[j]) )
}
}
return outputArray
}
So I want to create a suite of tests using XCTestCase. I usually use XCTestCase and XCTAssertEqual but these are only appropriate for C scalar types. This means that the following test case returns an error:
class pairsTests: XCTestCase {
func testNaive() {
measure {
XCTAssertEqual( pairsOfChars("abc") , [(Character("a"),Character("b")),(Character("a"),Character("c")),(Character("b"),Character("c")) ] )
}
}
}
I could convert to a string, but I'm thinking there is a better solution.
How can I test an output of an array of pairs of characters [(Character,Character)]
Your notion of a nonscalar is a total red herring. The problem is one of equatability.
How can I test an output of an array of pairs of characters [(Character,Character)]
You can't, because there is no default notion of what it would mean to equate two such arrays. This is the old "tuples of Equatable are not Equatable" problem (https://bugs.swift.org/browse/SR-1222) which still rears its head with arrays. The == operator works on tuples by a kind of magic, but they are still not formally Equatable.
You could define equatability of arrays of character pairs yourself:
typealias CharPair = (Character,Character)
func ==(lhs:[CharPair], rhs:[CharPair]) -> Bool {
if lhs.count != rhs.count {
return false
}
let zipped = zip(lhs,rhs)
return zipped.allSatisfy{$0 == $1}
}
Alternatively, have your pairsOfChars return something that is more easily made equatable, such as an array of a struct for which Equatable is defined.
For example:
struct CharacterPair : Equatable {
let c1:Character
let c2:Character
// in Swift 4.2 this next bit is not needed
static func ==(lhs:CharacterPair, rhs:CharacterPair) -> Bool {
return lhs.c1 == rhs.c1 && lhs.c2 == rhs.c2
}
}
func pairsOfChars(_ s: String) -> [CharacterPair] {
let strArray = Array(s)
var outputArray = [CharacterPair]()
for i in 0..<strArray.count - 1 {
for j in i + 1..<strArray.count {
outputArray.append(CharacterPair(c1:strArray[i],c2:strArray[j]))
}
}
return outputArray
}
You would then rewrite the test to match:
XCTAssertEqual(
pairsOfChars("abc"),
[CharacterPair(c1:Character("a"),c2:Character("b")),
CharacterPair(c1:Character("a"),c2:Character("c")),
CharacterPair(c1:Character("b"),c2:Character("c"))]
)

Swift - Unwrap optional in for in loop with where clause

I have a class with an optional member :
class A {
var i: Int? = nil
}
Then I have an array of objects of type A. Some objects in the array have a value for i, some others don't.
I want to iterate over objects in the array that have a value for i while unwrapping the optional at the same time. I didn't find a way to do both at the same time (I don't even know if it's possible), forcing me to write a if let construct inside the loop.
For example :
// a1, a2 have a value for i
let arr: [A] = [a1, a2, a3]
for obj in arr where obj.i != nil {
// I want to avoid if let, or force unwrapping here
if let unwrapped = obj.i {
print(i)
}
// let unwrapped = obj.i! ...
}
Is it possible in Swift ?
1.Maybe you can use flatMap to get value i, then print it
arr.flatMap{ $0.i }.forEach{ print($0) }
2.or Trying simple guard statement
arr.forEach { element in
guard let i = element.i else { return }
print(i)
}
I don't think that's possible.
Even if you have a where clause in your loop the type of obj is still of type A and as such i still remains optional.
To see why this is so think about the fact that you can change the value of i on object obj inside the loop, so the compiler is not sure that the value of i is valid until you unwrapp it.
You can try something like this
for obj in arr where obj.i != nil {
guard let i = obj.i else { continue }
print( i )
}
but if you start using guard you also skip the where clause
for obj in arr {
guard let i = obj.i else { continue }
print( i )
}
You can use case let syntax, but not without the help of map, and the result isn't the most readable:
for case let .some(unwrapped) in arr.map(\.i) {
print(unwrapped)
}
It's more useful if you're e.g. casting the outer object, e.g.:
for case let object as String in arrayOfAny {
if object.hasPrefix("tw") {
print("Starts with 'tw'")
}
}
instead of:
for object in arrayOfAny where object is String {
if object.hasPrefix("tw") { // Error: Value of type 'Any' has no member 'hasPrefix'
print("Starts with 'tw'")
}
}

Check if object is contained in Array [duplicate]

In Swift, how can I check if an element exists in an array? Xcode does not have any suggestions for contain, include, or has, and a quick search through the book turned up nothing. Any idea how to check for this? I know that there is a method find that returns the index number, but is there a method that returns a boolean like ruby's #include??
Example of what I need:
var elements = [1,2,3,4,5]
if elements.contains(5) {
//do something
}
Swift 2, 3, 4, 5:
let elements = [1, 2, 3, 4, 5]
if elements.contains(5) {
print("yes")
}
contains() is a protocol extension method of SequenceType (for sequences of Equatable elements) and not a global method as in
earlier releases.
Remarks:
This contains() method requires that the sequence elements
adopt the Equatable protocol, compare e.g. Andrews's answer.
If the sequence elements are instances of a NSObject subclass
then you have to override isEqual:, see NSObject subclass in Swift: hash vs hashValue, isEqual vs ==.
There is another – more general – contains() method which does not require the elements to be equatable and takes a predicate as an
argument, see e.g. Shorthand to test if an object exists in an array for Swift?.
Swift older versions:
let elements = [1,2,3,4,5]
if contains(elements, 5) {
println("yes")
}
For those who came here looking for a find and remove an object from an array:
Swift 1
if let index = find(itemList, item) {
itemList.removeAtIndex(index)
}
Swift 2
if let index = itemList.indexOf(item) {
itemList.removeAtIndex(index)
}
Swift 3, 4
if let index = itemList.index(of: item) {
itemList.remove(at: index)
}
Swift 5.2
if let index = itemList.firstIndex(of: item) {
itemList.remove(at: index)
}
Updated for Swift 2+
Note that as of Swift 3 (or even 2), the extension below is no longer necessary as the global contains function has been made into a pair of extension method on Array, which allow you to do either of:
let a = [ 1, 2, 3, 4 ]
a.contains(2) // => true, only usable if Element : Equatable
a.contains { $0 < 1 } // => false
Historical Answer for Swift 1:
Use this extension: (updated to Swift 5.2)
extension Array {
func contains<T>(obj: T) -> Bool where T: Equatable {
return !self.filter({$0 as? T == obj}).isEmpty
}
}
Use as:
array.contains(1)
If you are checking if an instance of a custom class or struct is contained in an array, you'll need to implement the Equatable protocol before you can use .contains(myObject).
For example:
struct Cup: Equatable {
let filled:Bool
}
static func ==(lhs:Cup, rhs:Cup) -> Bool { // Implement Equatable
return lhs.filled == rhs.filled
}
then you can do:
cupArray.contains(myCup)
Tip: The == override should be at the global level, not within your class/struct
I used filter.
let results = elements.filter { el in el == 5 }
if results.count > 0 {
// any matching items are in results
} else {
// not found
}
If you want, you can compress that to
if elements.filter({ el in el == 5 }).count > 0 {
}
Hope that helps.
Update for Swift 2
Hurray for default implementations!
if elements.contains(5) {
// any matching items are in results
} else {
// not found
}
(Swift 3)
Check if an element exists in an array (fulfilling some criteria), and if so, proceed working with the first such element
If the intent is:
To check whether an element exist in an array (/fulfils some boolean criteria, not necessarily equality testing),
And if so, proceed and work with the first such element,
Then an alternative to contains(_:) as blueprinted Sequence is to first(where:) of Sequence:
let elements = [1, 2, 3, 4, 5]
if let firstSuchElement = elements.first(where: { $0 == 4 }) {
print(firstSuchElement) // 4
// ...
}
In this contrived example, its usage might seem silly, but it's very useful if querying arrays of non-fundamental element types for existence of any elements fulfilling some condition. E.g.
struct Person {
let age: Int
let name: String
init(_ age: Int, _ name: String) {
self.age = age
self.name = name
}
}
let persons = [Person(17, "Fred"), Person(16, "Susan"),
Person(19, "Hannah"), Person(18, "Sarah"),
Person(23, "Sam"), Person(18, "Jane")]
if let eligableDriver = persons.first(where: { $0.age >= 18 }) {
print("\(eligableDriver.name) can possibly drive the rental car in Sweden.")
// ...
} // Hannah can possibly drive the rental car in Sweden.
let daniel = Person(18, "Daniel")
if let sameAgeAsDaniel = persons.first(where: { $0.age == daniel.age }) {
print("\(sameAgeAsDaniel.name) is the same age as \(daniel.name).")
// ...
} // Sarah is the same age as Daniel.
Any chained operations using .filter { ... some condition }.first can favourably be replaced with first(where:). The latter shows intent better, and have performance advantages over possible non-lazy appliances of .filter, as these will pass the full array prior to extracting the (possible) first element passing the filter.
Check if an element exists in an array (fulfilling some criteria), and if so, remove the first such element
A comment below queries:
How can I remove the firstSuchElement from the array?
A similar use case to the one above is to remove the first element that fulfils a given predicate. To do so, the index(where:) method of Collection (which is readily available to array collection) may be used to find the index of the first element fulfilling the predicate, whereafter the index can be used with the remove(at:) method of Array to (possible; given that it exists) remove that element.
var elements = ["a", "b", "c", "d", "e", "a", "b", "c"]
if let indexOfFirstSuchElement = elements.index(where: { $0 == "c" }) {
elements.remove(at: indexOfFirstSuchElement)
print(elements) // ["a", "b", "d", "e", "a", "b", "c"]
}
Or, if you'd like to remove the element from the array and work with, apply Optional:s map(_:) method to conditionally (for .some(...) return from index(where:)) use the result from index(where:) to remove and capture the removed element from the array (within an optional binding clause).
var elements = ["a", "b", "c", "d", "e", "a", "b", "c"]
if let firstSuchElement = elements.index(where: { $0 == "c" })
.map({ elements.remove(at: $0) }) {
// if we enter here, the first such element have now been
// remove from the array
print(elements) // ["a", "b", "d", "e", "a", "b", "c"]
// and we may work with it
print(firstSuchElement) // c
}
Note that in the contrived example above the array members are simple value types (String instances), so using a predicate to find a given member is somewhat over-kill, as we might simply test for equality using the simpler index(of:) method as shown in #DogCoffee's answer. If applying the find-and-remove approach above to the Person example, however, using index(where:) with a predicate is appropriate (since we no longer test for equality but for fulfilling a supplied predicate).
An array that contains a property that equals to
yourArray.contains(where: {$0.propertyToCheck == value })
Returns boolean.
The simplest way to accomplish this is to use filter on the array.
let result = elements.filter { $0==5 }
result will have the found element if it exists and will be empty if the element does not exist. So simply checking if result is empty will tell you whether the element exists in the array. I would use the following:
if result.isEmpty {
// element does not exist in array
} else {
// element exists
}
Swift 4/5
Another way to achieve this is with the filter function
var elements = [1,2,3,4,5]
if let object = elements.filter({ $0 == 5 }).first {
print("found")
} else {
print("not found")
}
As of Swift 2.1 NSArrays have containsObjectthat can be used like so:
if myArray.containsObject(objectImCheckingFor){
//myArray has the objectImCheckingFor
}
Array
let elements = [1, 2, 3, 4, 5, 5]
Check elements presence
elements.contains(5) // true
Get elements index
elements.firstIndex(of: 5) // 4
elements.firstIndex(of: 10) // nil
Get element count
let results = elements.filter { element in element == 5 }
results.count // 2
Just in case anybody is trying to find if an indexPath is among the selected ones (like in a UICollectionView or UITableView cellForItemAtIndexPath functions):
var isSelectedItem = false
if let selectedIndexPaths = collectionView.indexPathsForSelectedItems() as? [NSIndexPath]{
if contains(selectedIndexPaths, indexPath) {
isSelectedItem = true
}
}
if user find particular array elements then use below code same as integer value.
var arrelemnts = ["sachin", "test", "test1", "test3"]
if arrelemnts.contains("test"){
print("found") }else{
print("not found") }
Here is my little extension I just wrote to check if my delegate array contains a delegate object or not (Swift 2). :) It Also works with value types like a charm.
extension Array
{
func containsObject(object: Any) -> Bool
{
if let anObject: AnyObject = object as? AnyObject
{
for obj in self
{
if let anObj: AnyObject = obj as? AnyObject
{
if anObj === anObject { return true }
}
}
}
return false
}
}
If you have an idea how to optimize this code, than just let me know.
Swift
If you are not using object then you can user this code for contains.
let elements = [ 10, 20, 30, 40, 50]
if elements.contains(50) {
print("true")
}
If you are using NSObject Class in swift. This variables is according to my requirement. you can modify for your requirement.
var cliectScreenList = [ATModelLeadInfo]()
var cliectScreenSelectedObject: ATModelLeadInfo!
This is for a same data type.
{ $0.user_id == cliectScreenSelectedObject.user_id }
If you want to AnyObject type.
{ "\($0.user_id)" == "\(cliectScreenSelectedObject.user_id)" }
Full condition
if cliectScreenSelected.contains( { $0.user_id == cliectScreenSelectedObject.user_id } ) == false {
cliectScreenSelected.append(cliectScreenSelectedObject)
print("Object Added")
} else {
print("Object already exists")
}
what about using a hash table for the job, like this?
first, creating a "hash map" generic function, extending the Sequence protocol.
extension Sequence where Element: Hashable {
func hashMap() -> [Element: Int] {
var dict: [Element: Int] = [:]
for (i, value) in self.enumerated() {
dict[value] = i
}
return dict
}
}
This extension will work as long as the items in the array conform to Hashable, like integers or strings, here is the usage...
let numbers = Array(0...50)
let hashMappedNumbers = numbers.hashMap()
let numToDetect = 35
let indexOfnumToDetect = hashMappedNumbers[numToDetect] // returns the index of the item and if all the elements in the array are different, it will work to get the index of the object!
print(indexOfnumToDetect) // prints 35
But for now, let's just focus in check if the element is in the array.
let numExists = indexOfnumToDetect != nil // if the key does not exist
means the number is not contained in the collection.
print(numExists) // prints true
Swift 4.2 +
You can easily verify your instance is an array or not by the following function.
func verifyIsObjectOfAnArray<T>(_ object: T) -> Bool {
if let _ = object as? [T] {
return true
}
return false
}
Even you can access it as follows. You will receive nil if the object wouldn't be an array.
func verifyIsObjectOfAnArray<T>(_ object: T) -> [T]? {
if let array = object as? [T] {
return array
}
return nil
}
You can add an extension for Array as such:
extension Array {
func contains<T>(_ object: T) -> Bool where T: Equatable {
!self.filter {$0 as? T == object }.isEmpty
}
}
This can be used as:
if myArray.contains(myItem) {
// code here
}

Array transform having failable initialiser

I am using Swift 1.2 in Xcode 6.3.1
Following is my Person struct
struct Person {
let age: Int
init?(age: Int) { //Failable init
if age > 100 { return nil }
self.age = age
}
}
I am having a list of ages against which I have to make Person Objects.
I have made playground file.
let arr = Array(1...150) //Sample set of ages against which Person is created
var personList: [Person]!
and
personList = arr.map({ (val: Int) -> Person? in
return Person(age: val) //Makes object of type Person?
}).filter {
$0 != nil
}.map {
return $0!
}
Here I have uses map - filter - map because the first map invokes failable intializer, (hence it returns Person?) and personList is of type [Person].
Hence second function filters all the non nil objects and third map forcefully opens to optional therby making Person? to Person.
Is there a more easy/readable way out ? Chaining map-filter-map definitely seems to be an overkill for this
You can use flatMap to get rid of any nils in the array, this tutorial discusses the method in length, but the following will work best:
let personList = arr.flatMap { Person(age: $0) }
Note: This answer was given for Swift 1.2, the current
version at the time the question was posted. Since Swift 2 there is a better solution, see #Jeremie's answer.
I don't know of a built-in function that combines filter()
and map(). You can write the code slightly more compact using
the shorthand argument $0 in all closures:
let personList = arr.map { Person(age: $0) }
.filter { $0 != nil }
.map { $0! }
Of course you can define your own extension method which maps the
array elements and keeps only the non-nil results:
extension Array {
func optmap<U>(transform: T -> U?) -> [U] {
var result : [U] = []
for elem in self {
if let mapped = transform(elem) {
result.append(mapped)
}
}
return result
}
}
and then use it as
let personList = arr.optmap { Person(age: $0) }
You can use compactMap which is better that flatMap in this case to remove any nils in the array:
let personList = arr.compactMap { Person(age: $0) }
The Swift document declared:
Returns an array containing the non-nil results of calling the given
transformation with each element of this sequence.