3

I have several classes look like this:

class A {}
class A1 : A {}
class A2 : A {}

class A3 : A {}
class A4 : A {}

class main {
    var a1 : A1
    var a2 : A2
    var a3s : [A3]
    var a4s : [A4]

    func getAll() -> [A] {
        return ([a1, a2] + a3s + a4s)
    }
}

If you take a look on function getAll(), you will see I try to return an Array of all object with type is the base class A. However, I always get the error:

"Binary operator '+(::)' cannot be applied to operands of type '[Any]' and '[a3s]'"

Do you know what the proper way is in this case?

8
  • Duplicate of stackoverflow.com/questions/25146382/… Commented Jan 31, 2018 at 11:02
  • So, the expected output is: One array that contains all of the elements in the four arrays, is it correct? Commented Jan 31, 2018 at 11:02
  • 2
    Not really duplicated question. The difference is an array of type which is subclass of the base class. Commented Jan 31, 2018 at 11:05
  • Here is another one (just for the fun of it) return [[a1, a2], a3s, a4s].flatMap{ $0 as? [A] }.reduce([], +) or in more adventurous flavor return [[a1, a2], a3s, a4s].flatMap{ $0 as! [A] } Commented Jan 31, 2018 at 11:21
  • @Alladinian No need for the reduce. Also forced downcasts are evil. I have SwiftLint set up to mark that as a compile error. Commented Jan 31, 2018 at 11:26

3 Answers 3

4

I guess it's just a problem of casting correctly the arrays, you may solve it doing so:

func getAll() -> [A] {
    return ([a1, a2] as [A] + a3s as [A] + a4s)
}

Just for fun: if you want to solve it dynamically you might use the reflection in this way:

class A {}
class A1 : A {}
class A2 : A {}
class A3 : A {}
class A4 : A {}

class main {
    var a1 = A1()
    var a2 = A2()
    var a3s : [A3] = [A3()]
    var a4s : [A4] = [A4(), A4()]

    func getAll() -> [A] {
        var res = [A]()
        Mirror(reflecting:self).children.forEach {
            if let a = $0.value as? A {
                res.append(a)
            } else if let aArray = $0.value as? [A] {
                res.append(contentsOf: aArray)
            }
        }
        return res
    }
}

let all = main().getAll()
print(all) //A1, A2, A3, A4, A4
Sign up to request clarification or add additional context in comments.

4 Comments

@chipbk10 It's quite complicate :)
It's not really a complicated use case. There's no reason to have all those casts if you declare a variable with the correct type.
@Samah sure, however in the question is written Binary operator '+(::)' cannot be applied to operands of type '[Any]' and '[a3s]': my answer was trying to be pertinent to such question.
There's a difference between answering the question and solving the problem.
2

This is probably a cleaner way, I guess.

func getAll() -> [A] {
    let all: [[A]] = [[a1], [a2], a3s, a4s]
    return all.flatMap{$0}
}

This creates an array of arrays, then uses a flatMap to flatten them into a single array.

7 Comments

even, you code is cleaner, but I guess, the computation is expensive.
How is that expensive? A flatmap on a 4 element array.
but if each element array is consist of 1000 elements ?
Then you certainly shouldn't be appending arrays.
Slight nitpick,[[a1], [a2], a3s, a4s] can be written as [[a1, a2], a3s, a4s].
|
2

The problem is that the compiler thinks you are trying to add [Any] and [A3]. This is because the type of [a1, a2] is [Any]and a3s is [A3]. But the operator + cannot do that.

As others have pointed out, you can cast the arrays in your statement to [A] so the + operator can do his job.

Since you mentioned that you have several classes, it might be a good idea to create a function that you can reuse and that can infer the type correctly.

func merge<T>(_ array: [T]...) -> [T] {
    return array.flatMap { $0 }
}

func getAll() -> [A] {
    return merge([a1, a2], a3s, a4s)
}

2 Comments

Right. The take-away here is that an array of [ClassA, ClassB] is type [Any] unless you cast it, even if ClassA and ClassB have a common base class. It would be nice if, in your example, the compiler would make an array of [A1, A1] be type [A], but it doesn't.
Exactly, I am missing your first point in my answer. I don't understand the second part. Running array.forEach { print(type(of: $0)) } inside the merge(_:) function prints Array<A>. Even if you pass [a1, a1] as a parameter.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.