3

How to merge several arrays into one array with alternating values?

ex:

var arr1 = [1, 2, 3, 4, 5]
var arr2 = [a, b, c, d, e, f, g]
var arr3 = [aa, bb, cc, dd]

to

[1, a, aa, 2, b, bb, 3, c, cc, 4, d, dd, 5, e, f, g]

vacawama
  • 150,663
  • 30
  • 266
  • 294
gleb
  • 245
  • 2
  • 8
  • That's not what I wanted. See result array. – gleb Apr 28 '17 at 13:44
  • 2
    First : are `a`, `c`, `aa`, `bb` variables? Because you seem to be treating them like string literals. Second: what have you tried? https://stackoverflow.com/help/how-to-ask – msanford Apr 28 '17 at 13:51
  • @AntonVolkov, your edit used single quotes which are valid in Ruby or Python perhaps, but Swift uses double quotes for Character and String. – vacawama Apr 28 '17 at 14:37

6 Answers6

4

If the elements are of the same type in the 3 arrays, you could compute the maximum size and use flatMap to merge them.

For example:

var arr1 = ["1", "2", "3", "4", "5"]
var arr2 = ["a", "b", "c", "d", "e", "f", "g"]
var arr3 = ["aa", "bb", "cc", "dd","ee"]

let arrays    = [arr1,arr2,arr3]
let merged    = (0..<arrays.map{$0.count}.max()!)
               .flatMap{i in arrays.filter{i<$0.count}.map{$0[i]} }

[EDIT] Or if you want a more generalized solution for any number of arrays of a given type, you could create a function to do it:

func mergeArrays<T>(_ arrays:[T] ...) -> [T]
{ 
  return (0..<arrays.map{$0.count}.max()!)
         .flatMap{i in arrays.filter{i<$0.count}.map{$0[i]} }
}  

let merged = mergeArrays(arr1,arr2,arr3)            

[EDIT2] removed use of zip which simplified the expression a bit.

Keeping the zip approach here for future reference:

func mergeArrays<T>(_ arrays:[T] ...) -> [T]
{ 
   return arrays.reduce([[T]]())
   { zip($0,$1).map{$0+[$1]} + $0.dropFirst($1.count) + $1.dropFirst($0.count).map{[$0]} }
   .flatMap{$0}
}    

It could be somewhat faster. I haven't measured it

Alain T.
  • 40,517
  • 4
  • 31
  • 51
2

I made a func for you. You could use several different arrays as an argument

var arr1 = [1, 2, 3, 4, 5]
var arr2 = ["a", "b", "c", "d", "e", "f", "g"]
var arr3 = ["aa", "bb", "cc", "dd"]

func combine<T>(arrays:[[T]]) -> [T] {

    let maxCount = arrays.reduce(0) { max($0, $1.count) }

    var result = [T]()

    for i in 0..<maxCount {
        for array in arrays {
            if i < array.count {
                result.append(array[i])
            }
        }
    }

    return result

}

combine(arrays: [arr1,arr2,arr3] as [[Any]])
// or
combine(arrays: [arr2,arr3]) // if type is the same
Alex Shubin
  • 3,549
  • 1
  • 27
  • 32
  • Rather than using `Any` you should introduce a type parameter `T` as `func combine (arrays:[[T]]) -> [T] { ... }` – GoZoner Apr 28 '17 at 13:55
  • 1
    nope i shouldn't :) cause there's no generic fixed type "T", arrays might be of different types... – Alex Shubin Apr 28 '17 at 14:02
  • With `T` it would return the right type if all arrays have the same type. If they're different, call it with `let combined = combine(arrays: [arr1, arr2, arr3] as [[Any]])`. – vacawama Apr 28 '17 at 14:09
  • Ok, @vacawama you won. Seems it has sense to be done though. – Alex Shubin Apr 28 '17 at 14:17
  • As long as you give Swift something to go on it can figure it out `T`. For instance this works as well: `let result: [Any] = combine(arrays: [arr1,arr2,arr3])`. You'd want to use a protocol the arrays all implement or a common superclass would work. – vacawama Apr 28 '17 at 14:30
1

Try this:

extension Array {

    //Mutating
    mutating func weave(with array: Array) -> Array {
        precondition(!isEmpty && !array.isEmpty)
        var weavedArray = Array<Element>()
        weavedArray.reserveCapacity(count + array.count)
        var inputArray = array
        for _ in 0..<[count, array.count].min()! {
            weavedArray.append(self.removeFirst())
            weavedArray.append(inputArray.removeFirst())
        }
        let largerArr = largerOf(self, inputArray)
        if largerArr.count != 0 {
            weavedArray.append(contentsOf: largerArr)
        }
        self = weavedArray
        return weavedArray
    }

    //Non-mutating
    func weaved(with array: Array) -> Array {
        precondition(!isEmpty && !array.isEmpty)
        var weavedArray = Array<Element>()
        weavedArray.reserveCapacity(count + array.count)
        var selfArray = self
        var inputArray = array
        for _ in 0..<[count, array.count].min()! {
            weavedArray.append(selfArray.removeFirst())
            weavedArray.append(inputArray.removeFirst())
        }
        let largerArr = largerOf(selfArray, inputArray)
        if largerArr.count != 0 {
            weavedArray.append(contentsOf: largerArr)
        }
        return weavedArray
    }

    internal func largerOf<T>(_ arr1: Array<T>, _ arr2: Array<T>) -> Array<T> {
        switch (arr1.count, arr2.count) {
        case (let a, let b) where a > b: return arr1
        case (let a, let b) where a < b: return arr2
        case (let a, let b) where a == b: return arr1
        default: return arr2
        }
    }
}

Usage

Mutating - .weave(with: )

let odds = [1, 3, 5, 7, 9]
let evens = [2, 4, 6, 8, 10]

odds.weave(with: evens)

print(odds) //prints: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(evens) //prints: [2, 4, 6, 8, 10]

Non-Mutating - .weaved(with: )

let odds = [1, 3, 5, 7, 9]
let evens = [2, 4, 6, 8, 10]

let firstTen = odds.weaved(with: evens)

print(firstTen) // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(odds) //prints: [1, 3, 5, 7, 9]
priny(evens) //prints: [2, 4, 6, 8, 10]

Hope this helps, if you have any further questions, feel free to ask!

Noah Wilder
  • 1,656
  • 20
  • 38
0

No generic function for that I suppose but implementing it is rather simple:

    var sumArr: [Any] = []
    let max_count = max(arr1.count, arr2.count, arr3.count)
    for i in 0...max_count {
        if arr1.count > i { sumArr.append(arr1[i]) }
        if arr2.count > i { sumArr.append(arr2[i]) }
        if arr3.count > i { sumArr.append(arr3[i]) }
     }

or let's make it more reusable:

    func combineArrays(arrays: [[Any]]) -> [Any] {
        var sumArr: [Any] = []
        guard let max_count = arrays.map({$0.count}).max() else { return [] }
        for i in 0...max_count {
            for arr in arrays {
                if arr.count > i { sumArr.append(arr[i]) }
            }
        }
        return sumArr
    }
user3581248
  • 964
  • 10
  • 22
  • 2
    Your function should use a type parameter `T`, rather than `Any`, given that the elements don't matter and there is no reason to lose the type information by returning `Any`. `func combineArrays (arrays: [[T]]) -> [T] { ... }` – GoZoner Apr 28 '17 at 13:59
  • No need to use `map` to get the max count `arrays.max(by: {$0.count > $1.count})?.count` – Leo Dabus Apr 28 '17 at 14:40
  • While we are nitpicking; skip `map` and `max` on the result - this is a problem for `reduce` as: `arrays.reduce(0) { max($0, $1.count) } ` – GoZoner Apr 28 '17 at 21:24
  • @LeoDabus how exactly is your approach better than the one with `map`? – user3581248 Apr 29 '17 at 08:55
0

You can try this

var c = 0
var i = 0
var arr1 = [1, 2, 3, 4, 5]
var arr2 = [a, b, c, d, e, f, g]
var arr3 = [aa, bb, cc, dd]
var arr = [] 
while (c < 3) {
    if i < arr1.count {
        arr.append(arr1[i])
    } else {
        c += 1
    }
    if i < arr2.count {
        arr.append(arr2[i])
    } else {
        c += 1
    }
    if i < arr3.count {
        arr.append(arr3[i])
    } else {
        c += 1
    }
    i += 1
}
Farid Al Haddad
  • 833
  • 9
  • 19
0

This is my solution for you :

const arr1 = [1, 2, 3, 4, 5]
const arr2 = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
const arr3 = ['aa', 'bb', 'cc', 'dd']

const displayAlternatedValues = (a, b, c) => {
  const allArraysInOne = [a, b, c]
  let arrayOfLengths = []
  let arrayOfAlternatedValues = []

  for (let i = 0; i < allArraysInOne.length; i++) {
    const findMaxLength = arrayOfLengths.push(allArraysInOne[i].length)
  } 

  const maxLength = Math.max(... arrayOfLengths)
  // this function allows us to find the length of iteration for the next 'for'

  for (let i = 0; i < maxLength; i++) {
    const associateAlternedValues = [a[i], b[i], c[i]]
    const pushAlternedValues = arrayOfAlternatedValues.push(...associateAlternedValues)
  }
  // this function allows us to create a new array with values sorted by their index

  const cleanedAlternedValues = arrayOfAlternatedValues.filter(elem => typeof elem !=='undefined')

  // this variable create a clean array, without displaying 'undefined' in case there 
  // is no value matching a[i], b[i], c[i]

  return cleanedAlternedValues

}

displayAlternatedValues(arr1, arr2, arr3)

// result : [ 1, 'a', 'aa', 2, 'b', 'bb', 3, 'c', 'cc', 4, 'd', 'dd', 5, 'e', 'f', 'g' ]

Hope this will help ! I'm still learning !