3

I have multiple array (max 15), each can max have 1800 objects.

I need to combine them into a single array, so that I then can apply a delimiter (',') to generate a csv file. The problem is when I combine them into a single array, it has to be sequentially combined, like first objects of each array should be inserted initially followed by the 2nd index of objects then 3rd and so on.

I'm able to achieve the result I want by using a for-in loop. However this doesn't very swifty. I feel it can be done in a much cleaner way using functional methods available in swift (using map, reduce and filter functions).

However I'm not able to combine them perfectly. Can anyone help me with using the swift functional methods to achieve the result.

P.S: Let me know if you want me to post the for-in loop code, but I believe that's not required.

Grimxn
  • 22,115
  • 10
  • 72
  • 85
Rameswar Prasad
  • 1,331
  • 17
  • 35
  • Yes, saw it. I'm kind of experimenting with different solutions too as it seems to be a good problem. Will update with my answer if I find a better one over weekend, else will accept one of answers given below. Cheers!! :) – Rameswar Prasad Sep 27 '16 at 13:23

4 Answers4

5

Given 4 (or more) arrays

let list0: [Int] = [ 1, 2, 3, 6, 7, 8, 9 ]
let list1: [Int] = [ 10, 20, 30, 40, 50, 60, 70, 80, 90]
let list2: [Int] = [ 100, 200, 300, 400, 500, 600, 700, 800, 900]
let list3: [Int] = [ 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000 ]

enumerate each one and put them into another array

let lists = [
    list0.enumerate().map { (index: $0, array: 0, value: $1) },
    list1.enumerate().map { (index: $0, array: 1, value: $1) },
    list2.enumerate().map { (index: $0, array: 2, value: $1) },
    list3.enumerate().map { (index: $0, array: 3, value: $1) }
]

Now you can write

let sorted = lists
    .flatten()
    .sort { ($0.index, $0.array) < ($1.index, $1.array) }
    .map { $0.value }

[1, 10, 100, 1000, 2, 20, 200, 2000, 3, 30, 300, 3000, 6, 40, 400, 4000, 7, 50, 500, 5000, 8, 60, 600, 6000, 9, 70, 700, 7000, 80, 800, 8000, 90, 900, 9000]

Luca Angeletti
  • 58,465
  • 13
  • 121
  • 148
  • 1
    Perfect. I experimented with different values and different sizes of arrays too. Worked as intended. Thank you very much. Updating your last line to swift 3.0 syntax. – Rameswar Prasad Sep 26 '16 at 09:09
  • 1
    It works perfectly, however I got one doubt still after looking closely, how come the condition for sorted function is working? { $0.0 < $0.1 }, you're comparing between the offset and element of the same tuple, instead of comparing between the offset of the next element. Not able to digest that. :) – Rameswar Prasad Sep 26 '16 at 09:36
  • @Rameswar: Infact you are right! `$0.0 < $0.1` should be `$0.0 < $1.0`. Thank you. Just updated my code. – Luca Angeletti Sep 26 '16 at 09:41
  • Wait, but that doesn't give correct answer. I already tested. The order changes from 1, 10, 100, 1000 to 1, 10, 1000, 100. – Rameswar Prasad Sep 26 '16 at 09:42
  • @Rameswar: `$0.0 < $1.0` gives me this: [1, 10, 100, 2, 20, 200, 3, 30, 300] – Luca Angeletti Sep 26 '16 at 09:43
  • I took 4 arrays , and it didn't provide the right result, the earlier condition was providing perfect, so I just got confused. – Rameswar Prasad Sep 26 '16 at 09:44
  • @Rameswar: Can you give me the 4 arrays where you get the wrong result? – Luca Angeletti Sep 26 '16 at 09:45
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/124193/discussion-between-rameswar-and-appzyourlife). – Rameswar Prasad Sep 26 '16 at 09:46
2

I would consider making this an extension for arrays of arrays (although note you cannot do this directly, see this Q&A). You could then use a combination of reduce(_:_:) along with both flavours of flatMap(_:) in order to sequentially merge your arrays by iterating through the lengths of the inner collections and extracting the elements at each given index.

extension Array where Element : RandomAccessCollection, Element.Index == Int, Element.IndexDistance == Element.Index {

    func joinedByTransposing() -> [Element.Iterator.Element] {

        // The maximum length of the inner collections. Obviously if the 2D array is
        // guaranteed to be n*m, you can optimise by just taking the first inner
        // collection's count (and obviously you'll want to check that the array isn't empty first).
        let maxIndex = self.reduce(0, {$0 > $1.count ? $0 : $1.count})

        // Iterates through the max length of the inner collections, joining the restantant collections
        // from the transform below into a single array.
        return (0..<maxIndex).flatMap { index in

            // Iterate through each inner collection, getting the element at the current index of iteration,
            // or returning nil if the index is out of bounds. This flatMap will filter out any nils.
            // If the 2D array is guarenteed to be n*m, this can be replaced by self.map { $0[index] }
            self.flatMap { innerArray in

                // Simple bounds check to get the element at the given index, or nil if out of bounds
                index < innerArray.count ? innerArray[index] : nil
            }
        }
    }
}

let array0 = [1,   2,   3,   4   ]
let array1 = [10,  20,  30       ]
let array2 = [100, 200, 300, 6, 7]

let result = [array0, array1, array2].joinedByTransposing()

print(result)

// [1, 10, 100, 2, 20, 200, 3, 30, 300, 4, 6, 7]

It’s worth noting that this solution has an overall time complexity of O(n * m) – whereas solutions that utilise sorted(by:) will have a time complexity of at least O(n * m * log(n * m)). For large arrays, this extra cost may well be non-trivial.

Community
  • 1
  • 1
Hamish
  • 78,605
  • 19
  • 187
  • 280
0

Here is my functional approach to the problem that you describe in your post.

First I flatted the array using the enumerated method, which return a tuple with the element position inside the array and It's value.

After this, You have an array with this tuples, next step, sort this big array by the offset (position) value of each element.

Once the array is sorted you hace to extract the value with map function.

And final step, once we have an array with sorted values, You have to reduce it to a string with the reduce function

// A group of arrays
var array1: [Int] = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
var array2: [Int] = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
var array3: [Int] = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
var array4: [Int] = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]

// This is **your** array
let bigOne = [ array1, array2, array3, array4 ]

// And here is the functional concatenation.
let flattedOne = bigOne.flatMap({ $0.enumerated() }).sorted(by: { $0.0 < $0.1 }).map({$0.element}).reduce("") 
{
    return $0.isEmpty ? "\($1)" : "\($0), \($1)"

}


print(flattedOne)
Adolfo
  • 1,862
  • 13
  • 19
  • I tried to experiment with this, it gives wrong output after sorted function. I changed the identical values to contain different value. E.g: first array contains 1, 2, 3... ; second array contains 10, 20, 30... ; third 100, 200, 300,.. and fourth 1000, 2000, 3000... etc. After sorted function it comes like, 1, 10, 1000, 100 where as it should be 1, 10, 100, 1000. The order gets wrong here. – Rameswar Prasad Sep 26 '16 at 08:41
  • Hey, can you join us in chat here --> http://chat.stackoverflow.com/rooms/124193/discussion-between-rameswar-and-appzyourlife – Rameswar Prasad Sep 26 '16 at 09:56
0

Here's another approach...

public func sort(compound array: [[Int]]) -> [Int]
{
    let max_index: Int = array.map({ $0.count }).max()!

    var sorted: [Int] = [Int]()


    (0 ..< max_index).forEach({ index in
        array.forEach()
        { 
            if $0.count > index
            {
                sorted.append($0[index])
            }
        }
    })

   return sorted
}


// A group of arrays
var array1: [Int] = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 111, 112 ]
var array2: [Int] = [ 10, 20, 3, 4, 5, 6, 7, 8, 9, 10 ]
var array3: [Int] = [ 1000, 2000, 3, 4, 5, 600, 7, 8, 9, 10, 11 ]
var array4: [Int] = [ 100, 200, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 ]

let bigOne: [[Int]] = [ array1, array2, array3, array4 ]

let sorted: [Int] = sort(compound: bigOne)
print(sorted)

And if you want the array as a CSV string...

print(sorted.reduce("") { return $0.isEmpty ? "\($1)" : "\($0), \($1)" })
Adolfo
  • 1,862
  • 13
  • 19