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?

Cœur
  • 37,241
  • 25
  • 195
  • 267
chipbk10
  • 5,783
  • 12
  • 49
  • 85
  • Duplicate of https://stackoverflow.com/questions/25146382/how-do-i-concatenate-or-merge-arrays-in-swift – Samah Jan 31 '18 at 11:02
  • So, the expected output is: One array that contains all of the elements in the four arrays, is it correct? – Ahmad F Jan 31 '18 at 11:02
  • 2
    Not really duplicated question. The difference is an array of type which is subclass of the base class. – chipbk10 Jan 31 '18 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] }` – Alladinian Jan 31 '18 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. – Samah Jan 31 '18 at 11:26
  • @Samah Well, you just did break down the 'fun' & 'adventurous' parts of my comment :P Agreed. That's why I didn't post an answer. – Alladinian Jan 31 '18 at 11:30
  • @Alladinian I'm never fun nor adventurous. ;) – Samah Jan 31 '18 at 11:31
  • Do you how to merge two arrays so that any element of one array that are found in the other array shows up only once the the resultant single array? – daniel Aug 09 '22 at 04:37

3 Answers3

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
mugx
  • 9,869
  • 3
  • 43
  • 55
  • 1
    @chipbk10 It's quite complicate :) – mugx Jan 31 '18 at 11:15
  • 1
    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 Jan 31 '18 at 11:21
  • @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. – mugx Jan 31 '18 at 11:29
  • There's a difference between answering the question and solving the problem. – Samah Jan 31 '18 at 11:30
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.

Samah
  • 1,224
  • 2
  • 13
  • 15
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)
}
Yannick
  • 3,210
  • 1
  • 21
  • 30
  • 1
    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. – Duncan C Jan 31 '18 at 23:33
  • 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`. Even if you pass `[a1, a1]` as a parameter. – Yannick Feb 01 '18 at 09:42