【问题标题】:Split Big Array Into Two Arrays将大数组拆分为两个数组
【发布时间】:2015-05-17 09:03:55
【问题描述】:

我有一个很大的对象数组,想把它分成两个数组,其中包含交替顺序的对象。

例子:

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

变成这两个数组(它们应该交替)

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

有很多方法可以拆分数组。但是,如果数组很大,什么是最有效(成本最低)的。

【问题讨论】:

  • 你会得到最好的结果是O(n)。只需创建两个新数组并循环遍历旧数组,在每次迭代中交替放置一个元素。

标签: arrays swift


【解决方案1】:

大/巨大的数组在部分处理时总是会出现问题,就像在这种情况下,因为创建两个额外的(即使是一半大小的)数组可能既耗时又耗内存。例如,如果您只想计算奇数和偶数位置的均值和标准差,但这需要调用一个需要序列作为输入的专用函数?

因此为什么不创建两个子集合,而不是复制数组内容,它们指向原始数组,以透明的方式允许查询它们的元素:

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)
    }
}

Collection 扩展和关联的结构将创建一个伪数组,您可以使用该伪数组仅访问您感兴趣的元素。

用法很简单:

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

使用上述方法,您可以迭代这两个步骤,而不必担心内存量会增加一倍。如果你真的需要两个子数组,你只需Array(stride)-ify 它们。

【讨论】:

    【解决方案2】:

    我只需要这样做,我将一个数组分成两个在一个地方,三个分成另一个。所以我建立了这个:

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

    它适用于 Swift 4.2 和 5.0(从带有 Xcode 10.2 beta 2 的 5.0 开始)

    【讨论】:

      【解决方案3】:

      更简洁、更实用的方法是使用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]
      

      【讨论】:

        【解决方案4】:

        您可以使用 for in stride 循环来填充两个结果数组,如下所示:

        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]
        

        更新: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)
            }
        }
        

        【讨论】:

        • 如果有人在看这段代码在 Swift 2 中不起作用.. stride(from: to: by:) 不存在并且 [T] 更改为 [Element]
        【解决方案5】:

        使用过滤器有多种花哨的方法,但大多数可能需要两次而不是一次,因此您不妨只使用 for 循环。

        在这种情况下,预先保留空间可能会产生很大的不同,因为如果源很大,它将避免随着新数组的增长而不必要的重新分配,并且所需空间的计算是在数组上以恒定时间计算的。

        // 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])
        

        如果性能确实很关键,您可以使用source.withUnsafeBufferPointer 访问源元素,以避免索引边界检查。

        如果数组真的很大,并且除了对少量元素进行采样之外,您不会使用结果数据,您可以考虑改用惰性视图(尽管 std lib惰性过滤器在这里用处不大,因为它返回的是序列而不是集合——你可能需要自己编写)。

        【讨论】:

        • 提供了许多知识渊博的答案。我选择了这个,因为它更进一步地解释了提高性能的方法。
        【解决方案6】:

        在我看来,这是最简单的方法

        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()
        

        【讨论】:

          【解决方案7】:

          使用 for 循环。如果索引值为偶数,则将其发送到一个数组,如果索引值为奇数,则将其发送到奇数数组。

          【讨论】:

            猜你喜欢
            • 2012-04-13
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-06-25
            相关资源
            最近更新 更多