1

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 ...

Vyachaslav Gerchicov
  • 2,317
  • 3
  • 23
  • 49
  • output is sorted after merging? – Tushar Sharma Jun 22 '21 at 12:32
  • @TusharSharma In the result a2 always comes as [a1, b1, c1, a2, ...]. If for example b array is empty then the result should be [a1, c1, a2, ...]. In other words the result stores the relative order of elements from initial arrays but no additional sort is applied in the end. – Vyachaslav Gerchicov Jun 23 '21 at 09:44
  • How do I do this to yield a single array with any duplicate elements appearing only once in the resultant array? – daniel Aug 09 '22 at 04:40

4 Answers4

2

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"]

Joakim Danielson
  • 43,251
  • 5
  • 22
  • 52
1

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)
Sweeper
  • 213,210
  • 22
  • 193
  • 313
0

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"]
Tarun Tyagi
  • 9,364
  • 2
  • 17
  • 30
-1

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"]

Raja Kishan
  • 16,767
  • 2
  • 26
  • 52
  • It seems you can replace inner `arr` and `map` with `compactMap` and return `nil` inside it when index exceeds the array size – Vyachaslav Gerchicov Jun 23 '21 at 10:06
  • Yes, I can. at that time I was in a hurry so I posted. This is the same logic as the above two ans @VyachaslavGerchicov – Raja Kishan Jun 23 '21 at 10:10
  • `map` is designed to return a collection of modified items but instead you ignore its result and use it like `forEach`. So why not to use `map` properly or replace it with `forEach` for example? – Vyachaslav Gerchicov Jun 23 '21 at 13:32
  • @VyachaslavGerchicov I prefer a map because of the design for fast iteration. You should need to do some research : https://albinek.com/map-method-vs-for-loop – Raja Kishan Jun 23 '21 at 13:36
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/234117/discussion-between-vyachaslav-gerchicov-and-raja-kishan). – Vyachaslav Gerchicov Jun 23 '21 at 14:03