Home > Mobile >  Why can compactMap not remove nil values from an array of optional Any?
Why can compactMap not remove nil values from an array of optional Any?

Time:11-26

I have this simple code, what I am trying to do is removing nil values from [Any?] and make an [Any]. But compactMap does not work!

let optionalString: String? = nil
let optionalInt: Int? = nil

let customTuple = (optionalString, "a", "b", "c", optionalInt)

let arrayOfChildren: [Mirror.Child] = Array(Mirror(reflecting: customTuple).children)

let arrayOfChildrenValues: Array<Any?> = arrayOfChildren.map({ element in element.value })

let compactArrayOfChildrenValues: Array<Any> = arrayOfChildrenValues.compactMap({ element in element })

print(compactArrayOfChildrenValues)

The printed result:

[nil, "a", "b", "c", nil]


Test:

For example, the following simple test would work! But my code in top does not! What I am missing here?

let optionalString: String? = nil
let optionalInt: Int? = nil
let array: [Any?] = [optionalString, "Hello, world!", optionalInt, 1000]
print(array.compactMap({ element in element }))

result:

["Hello, world!", 1000]


Another Test:

let optionalString: String? = nil
let optionalInt: Int? = nil

let arrayOfChildrenValues: Array<Any?> = [optionalString, "a", "b", "c", optionalInt]
let compactArrayOfChildrenValues: Array<Any> = arrayOfChildrenValues.compactMap({ element in element })

print(compactArrayOfChildrenValues)

result:

["a", "b", "c"]

CodePudding user response:

Firstly, change the type of arrayOfChildrenValues to [Any], so it is no longer optional, since \.value doesn't return an optional.

Now - you want to see if the element of type Any is an optional. To do this, you can try match it to Optional<Any>.none.

Change the following parts:

let arrayOfChildrenValues: [Any] = arrayOfChildren.map(\.value)

let compactArrayOfChildrenValues: [Any] = arrayOfChildrenValues.filter { element in
    switch element {
    case Optional<Any>.none: return false
    default: return true
    }
}

Other ways

Here is another way:

let arrayOfChildrenValues: [Any] = arrayOfChildren.map(\.value)

let compactArrayOfChildrenValues: [Any] = (arrayOfChildrenValues as [Any?]).compactMap { $0 }

However, there is another method with some strange behaviour happening, I'm not sure I can fully understand.

Here's the code:

let arrayOfChildrenValues: [Any?] = arrayOfChildren.map(\.value)

let compactArrayOfChildrenValues: [Any] = arrayOfChildrenValues.compactMap { $0 }

The reason why this is strange is because all the other methods work with arrayOfChildrenValues being expressed like { element in element.value }, however this only works when using key-paths. Perhaps it's because the return type is inferred as [Any?], so it does a cast similar to the example above?

Why does your other example work?

The reason why compactMap doesn't work on [Any] is because a value of type Any cannot be not nil, since it is not an optional.

You can test this, by trying compactMap on [Any] instead of [Any?]. Modify your other example, as so:

let anyArray: [Any] = array.map { $0 as Any }
print(anyArray.compactMap({ element in element }))

Notice this prints [nil, Optional("Hello, world!"), nil] - and has also wrapped all our values in an optional because in reality this is still of type [Any?], 'disguised' as [Any].

  • Related