Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Map only non-nil values

Tags:

I am serialising some json into objects with a failable json initialiser like this:

 sections = {
        let sectionJsons = json["sections"] as! [[String:AnyObject]]
        return sectionJsons.map {
            DynamicSection($0)
        }
    }()

DynamicSection's init:

init?(_ json:[String:AnyObject]) {
    super.init()
    //Boring stuff that can fail

I want to only append the DynamicSections that passed the init to sections. How can I accomplish this?

I can use filter+map like

return sectionJsons.filter { DynamicSection($0) != nil }.map { DynamicSection($0)! }

But that leads to initing the DynamicSection twice, which i'd like to avoid. Is there any better way to do this?

like image 272
Oscar Apeland Avatar asked Dec 16 '16 10:12

Oscar Apeland


People also ask

How do I get rid of nil?

Syntax: Array. compact() Parameter: Array to remove the 'nil' value from. Return: removes all the nil values from the array.

What is compact map?

compactMap helps you to eliminate nil values and map at the same time. It takes your sequence and produces a fancier sequence. Easy as it is. Here is the definition of compactMap . compactMap returns an array containing the non-nil results of calling the given transformation with each element of this sequence.

What flatMap Swift?

flatMap(hashtags) print(tags) // ["#swiftui", "#combine", "#wwdc"] So map transforms an array of values into an array of other values, and flatMap does the same thing, but also flattens a result of nested collections into just a single array.


2 Answers

You can use flatMap:

return sectionJsons.flatMap { DynamicSection($0) }

Example:

struct Foo {
    let num: Int
    init?(_ num: Int) {
        guard num % 2 == 0 else { return nil }
        self.num = num
    }
}

let arr = Array(1...5) // odd numbers will fail 'Foo' initialization
print(arr.flatMap { Foo($0) }) // [Foo(num: 2), Foo(num: 4)]

// or, point to 'Foo.init' instead of using an anonymous closure
print(arr.flatMap(Foo.init))   // [Foo(num: 2), Foo(num: 4)]

Whenever you see a chained filter and map, flatMap can generally be used as a good alternative approach (not just when using the filter to check nil entries).

E.g.

// non-init-failable Foo
struct Foo {
    let num: Int
    init(_ num: Int) {
        self.num = num
    }
}

let arr = Array(1...5) // we only want to use the even numbers to initialize Foo's

// chained filter and map
print(arr.filter { $0 % 2 == 0}.map { Foo($0) })   // [Foo(num: 2), Foo(num: 4)]

// or, with flatMap
print(arr.flatMap { $0 % 2 == 0 ? Foo($0) : nil }) // [Foo(num: 2), Foo(num: 4)]
like image 130
dfrib Avatar answered Sep 24 '22 20:09

dfrib


For Swift 3.0 and above:

return sectionJsons.compactMap { DynamicSection($0) }
like image 31
pierre23 Avatar answered Sep 26 '22 20:09

pierre23