Onichan
Onichan

Reputation: 4516

Split Big Array Into Two Arrays

I have a big array of objects and would like to split it into two arrays containing the objects in alternate order.

Example:

[0, 1, 2, 3, 4, 5, 6]

Becomes these two arrays (they should alternate)

[0, 2, 4, 6] and [1, 3, 5]

There are a ton of ways to split an array. But, what is the most efficient (least costly) if the array is huge.

Upvotes: 6

Views: 6508

Answers (7)

Cristik
Cristik

Reputation: 32827

Big/huge array always pose problems when being partially processed, like in this case, as creating two extra (even if half-sized) arrays can be both time and memory consuming. What if, for example, you just want to compute the mean and standard deviation of oddly and evenly positioned numbers, but this will require calling a dedicated function which requires a sequence as input?

Thus why not creating two sub-collections that instead of duplicating the array contents, they point to the original array, in a transparent manner to allow querying them for elements:

extension Collection where Index: Strideable{
    func stride(from: Index, to: Index, by: Index.Stride) -> StridedToCollection<Self> {
        return StridedToCollection(self, from: from, to: to, by: by)
    }
}

struct StridedToCollection<C>: Collection where C: Collection, C.Index: Strideable {
    private let _subscript : (C.Index) -> C.Element
    private let step: C.Index.Stride

    fileprivate init(_ collection: C, from: C.Index, to: C.Index, by: C.Index.Stride)  {
        startIndex = from
        endIndex = Swift.max(to, startIndex)
        step = by
        _subscript = { collection[$0] }
    }

    let startIndex: C.Index
    let endIndex: C.Index

    func index(after i: C.Index) -> C.Index {
        let next = i.advanced(by: step)
        return next >= endIndex ? endIndex : next
    }

    subscript(_ index: C.Index) -> C.Element {
        return _subscript(index)
    }
}

The Collection extension and the associated struct would create a pseudo-array that you can use to access only the elements you are interested into.

Usage is simple:

let numbers: [Int] = [1, 2, 3, 4]
let stride1 = numbers.stride(from: 0, to: numbers.count, by: 2)
let stride2 = numbers.stride(from: 1, to: numbers.count, by: 2)
print(Array(stride1), Array(stride2))

With the above you can iterate the two strides without worrying you'll double the amount of memory. And if you actually need two sub-arrays, you just Array(stride)-ify them.

Upvotes: 1

jamone
jamone

Reputation: 17421

I just had to do this where I split an array into two in one place, and three into another. So I built this:

extension Array {
    /// Splits the receiving array into multiple arrays
    ///
    /// - Parameter subCollectionCount: The number of output arrays the receiver should be divided into
    /// - Returns: An array containing `subCollectionCount` arrays. These arrays will be filled round robin style from the receiving array.
    ///            So if the receiver was `[0, 1, 2, 3, 4, 5, 6]` the output would be `[[0, 3, 6], [1, 4], [2, 5]]`. If the reviever is empty the output
    ///            Will still be `subCollectionCount` arrays, they just all will be empty. This way it's always safe to subscript into the output.
    func split(subCollectionCount: Int) -> [[Element]] {
        precondition(subCollectionCount > 1, "Can't split the array unless you ask for > 1")
        var output: [[Element]] = []

        (0..<subCollectionCount).forEach { (outputIndex) in
            let indexesToKeep = stride(from: outputIndex, to: count, by: subCollectionCount)
            let subCollection = enumerated().filter({ indexesToKeep.contains($0.offset)}).map({ $0.element })
            output.append(subCollection)
        }

        precondition(output.count == subCollectionCount)
        return output
    }
}

It works on Swift 4.2 and 5.0 (as of 5.0 with Xcode 10.2 beta 2)

Upvotes: 0

chasew
chasew

Reputation: 8828

A more concise, functional approach would be to use reduce

let a = [0,1,2,3,4,5,6]

let (evens, odds) = a.enumerate().reduce(([Int](),[Int]())) { (cur, next) in
    let even = next.index % 2 == 0
    return (cur.0 + (even ? [next.element] : []),
            cur.1 + (even ? [] : [next.element]))
}

evens // [0,2,4,6]
odds // [1,3,5]

Upvotes: 4

Leo Dabus
Leo Dabus

Reputation: 236380

You can use the for in stride loop to fill two resulting arrays as follow:

extension Array {
    var groupOfTwo:(firstArray:[T],secondArray:[T]) {
        var firstArray:[T] = []
        var secondArray:[T] = []
        for index in stride(from: 0, to: count, by: 2) {
            firstArray.append(self[index])
            if index + 1 < count {
                secondArray.append(self[index+1])
            }
        }
        return (firstArray,secondArray)
    }
}



[0, 1, 2, 3, 4, 5, 6].groupOfTwo.firstArray   // [0, 2, 4, 6]
[0, 1, 2, 3, 4, 5, 6].groupOfTwo.secondArray  // [1, 3, 5]

update: Xcode 7.1.1 • Swift 2.1

extension Array {
    var groupOfTwo:(firstArray:[Element],secondArray:[Element]) {
        var firstArray:[Element] = []
        var secondArray:[Element] = []
        for index in 0.stride(to: count, by: 2) {
            firstArray.append(self[index])
            if index + 1 < count {
                secondArray.append(self[index+1])
            }
        }
        return (firstArray,secondArray)
    }
}

Upvotes: 4

Airspeed Velocity
Airspeed Velocity

Reputation: 40965

There are various fancy ways to do it with filter but most would probably require two passes rather than one, so you may as well just use a for-loop.

Reserving space up-front could make a big difference in this case since if the source is large it’ll avoid unnecessary re-allocation as the new arrays grow, and the calculation of space needed is in constant time on arrays.

// could make this take a more generic random-access collection source
// if needed, or just make it an array extension instead
func splitAlternating<T>(source: [T]) -> ([T],[T]) {
    var evens: [T] = [], odds: [T] = []

    evens.reserveCapacity(source.count / 2 + 1)
    odds.reserveCapacity(source.count / 2)

    for idx in indices(source) {
        if idx % 2 == 0 {
            evens.append(source[idx])
        }
        else {
            odds.append(source[idx])
        }
    }

    return (evens,odds)
}

let a = [0,1,2,3,4,5,6]
splitAlternating(a)  // ([0, 2, 4, 6], [1, 3, 5])

If performance is truly critical, you could use source.withUnsafeBufferPointer to access the source elements, to avoid the index bounds checking.

If the arrays are really huge, and you aren’t going to use the resulting data except to sample a small number of elements, you could consider using a lazy view instead (though the std lib lazy filter isn’t much use here as it returns sequence not a collection – you’d possibly need to write your own).

Upvotes: 4

user4665496
user4665496

Reputation:

Here's, in my opinion, the easiest way

old_list = [0, 1, 2, 3, 4, 5, 6]
new_list1 =[]
new_list2 = []
while len(old_list)>0:
    new_list1.append(old_list.pop(-1))
    if len(old_list) != 0:
        new_list2.append(old_list.pop(-1))

new_list1.reverse()
new_list2.reverse()

Upvotes: 0

Ajoy
Ajoy

Reputation: 113

Use for loops. If the index value is even then send that to one array and if the index value is odd, then send that to odd array.

Upvotes: 0

Related Questions