Vyachaslav Gerchicov
Vyachaslav Gerchicov

Reputation: 2457

Merge multiple arrays into one by mixing elements in Swift?

Example:

let a = [a1, a2, a3]
let b = [b1, b2]
let c = [c1, c2, c3, c4]

I need the following result:

[a1, b1, c1, a2, b2, c2, a3, c3, c4]

a1, b1, ... - any objects of the same type

My current solution is to create mutable copies of these arrays and call popFirst sequentially on each array in the specified order until all the arrays become empty.

But is it possible to solve this task by using inner Swift features without of iterating manually? For example like the following code:

[a, b, c].map { ... }.filter { ... }.reduce ...

Upvotes: 1

Views: 2506

Answers (4)

Joakim Danielson
Joakim Danielson

Reputation: 51872

Here is a solution using a basic for loop but it is flexible in the number of arrays you can merge

func merge<T>(_ arrays: [T]...) -> [T] {
    guard let longest = arrays.max(by: { $0.count < $1.count })?.count else { return [] }
    var result = [T]()
    for index in 0..<longest {
        for array in arrays {
            guard index < array.count else { continue }
            result.append(array[index])
        }
    }
    return result
}

Example from question

print(merge(a, b, c))

["a1", "b1", "c1", "a2", "b2", "c2", "a3", "c3", "c4"]

Upvotes: 2

Sweeper
Sweeper

Reputation: 270860

Think of this as "transposing" the 2D array [a, b, c], except when there isn't an element, you just ignore it and move on. This reminds me of this question of mine, except that you want the inner arrays to have different sizes.

We can modify Alexander's answer there to suit your needs by finding the inner array with the largest count, and use that instead of the first inner array. We also change the outer map to flatMap since you want it flattened.

We also introduce a safe: subscript, so that compactMap can be used to ignore those "missing" elements.

extension Collection where Self.Element: RandomAccessCollection {
    
    func transposed() -> [Self.Element.Element] {
        guard let rowWithMaxElems = self.max(by: { $0.count < $1.count }) else { return [] }
        return rowWithMaxElems.indices.flatMap { index in
            self.compactMap { $0[safe: index] }
        }
    }
}

extension RandomAccessCollection {
    subscript(safe index: Index) -> Element? {
        get {
            indices.contains(index) ? self[index] : nil
        }
    }
}

let a = [1, 2, 3]
let b = [4, 5]
let c = [6, 7, 8, 9]

let result = [a, b, c].transposed()
print(result)

Upvotes: 1

Raja Kishan
Raja Kishan

Reputation: 18904

You can do by this

let a = ["a1", "a2", "a3"]
let b = ["b1", "b2"]
let c = ["c1", "c2", "c3", "c4"]

let initArr = [a,b,c]
let maxCount = initArr.max(by: {$0.count < $1.count})?.count ?? 0

let newArr: [String] = (0..<maxCount).flatMap { (index) -> [String]in
    var arr: [String] = []
    _ = initArr.indices.map { (number)in
        if index < initArr[number].count {
            arr.append(initArr[number][index])
        }
    }
    return arr
}
print(newArr) // ["a1", "b1", "c1", "a2", "b2", "c2", "a3", "c3", "c4"]

Upvotes: -1

Tarun Tyagi
Tarun Tyagi

Reputation: 10092

Try this -

let a = ["a1", "a2", "a3"]
let b = ["b1", "b2"]
let c = ["c1", "c2", "c3", "c4"]
        
var d: [String] = []
let count = max(a.count, b.count, c.count)
for i in 0..<count {
    if i < a.count { d.append(a[i]) }
    if i < b.count { d.append(b[i]) }
    if i < c.count { d.append(c[i]) }
}
print(d)

Output -

["a1", "b1", "c1", "a2", "b2", "c2", "a3", "c3", "c4"]

Upvotes: 0

Related Questions