请考虑以下两个排序数组:
let arr1 = [1, 7, 17, 25, 38]
let arr2 = [2, 5, 17, 29, 31]
简单来说,预期结果应该是:
[1, 2, 5, 7, 17, 17, 25, 29, 31, 38]
实际上,如果我们尝试对此问题进行简单的研究,则会发现许多资源提供了以下“典型”方法:
func mergedArrays(_ array1: [Int], _ array2: [Int]) -> [Int] {
var result = [Int]()
var i = 0
var j = 0
while i < array1.count && j < array2.count {
if array1[i] < array2[j] {
result.append(array1[i])
i += 1
} else {
result.append(array2[j])
j += 1
}
}
while i < array1.count {
result.append(array1[i])
i += 1
}
while j < array2.count {
result.append(array2[j])
j += 1
}
return result
}
因此:
let merged = mergedArrays(arr1, arr2) // [1, 2, 5, 7, 17, 17, 25, 29, 31, 38]
这是完全可行的。
但是,我的问题是:
如果我们尝试通过更多“快捷”的速记解决方案来实现它,那会是什么?
请注意,其操作方式如下:
let merged = Array(arr1 + arr2).sorted()
不会那么聪明,因为它应该像O(n)
那样完成。
答案 0 :(得分:9)
我试图在功能编程和无变量中解决您的问题。
给出2个数组
let nums0 = [1, 7, 17, 25, 38]
let nums1 = [2, 5, 17, 29, 31]
我们将第一个与第二个的反向版本连接起来
let all = nums0 + nums1.reversed()
结果将是这种金字塔。
[1, 7, 17, 25, 38, 31, 29, 17, 5, 2]
现在,如果我们一一挑选出边缘(左侧或右侧)上的最小元素,我们将保证按升序选择所有元素。
[1, 7, 17, 25, 38, 31, 29, 17, 5, 2] -> we pick 1 (left edge)
[7, 17, 25, 38, 31, 29, 17, 5, 2] -> we pick 2 (right edge)
[7, 17, 25, 38, 31, 29, 17, 5] -> we pick 5 (right edge)
[7, 17, 25, 38, 31, 29, 17] -> we pick 7 (left edge)
[17, 25, 38, 31, 29, 17] -> we pick 17 (right edge)
[17, 25, 38, 31, 29] -> we pick 17 (left edge)
[25, 38, 31, 29] -> we pick 25 (left edge)
[38, 31, 29] -> we pick 29 (right edge)
[38, 31] -> we pick 31 (right edge)
[38] -> we pick 38 (both edges)
现在让我们看一下我们构建的数组,挑选所有这些元素。
We selected 1: [1]
We selected 2: [1, 2]
We selected 5: [1, 2, 5]
We selected 7: [1, 2, 5, 7]
We selected 17: [1, 2, 5, 7, 17]
We selected 17: [1, 2, 5, 7, 17, 17]
We selected 25: [1, 2, 5, 7, 17, 17, 25]
We selected 29: [1, 2, 5, 7, 17, 17, 25, 29]
We selected 31: [1, 2, 5, 7, 17, 17, 25, 29, 31]
We selected 38: [1, 2, 5, 7, 17, 17, 25, 29, 31, 38]
这看起来像我们想要实现的结果吗?
现在是时候编写一些Swifty代码了。
这是代码
let merged = all.reduce((all, [Int]())) { (result, elm) -> ([Int], [Int]) in
let input = result.0
let output = result.1
let first = input.first!
let last = input.last!
// I know these ☝️ force unwraps are scary but input will never be empty
if first < last {
return (Array(input.dropFirst()), output + [first])
} else {
return (Array(input.dropLast()), output + [last])
}
}.1
1。
我们将包含all
数组和空数组的元组传递给reduce。
all.reduce((all, [Int]()))
我们将调用第一个数组
input
和第二个数组output
。 逐步缩小将删除input
边缘的最小元素,并将其追加到output
。
2。然后,在闭包内部,为out元组的2个元素赋予适当的名称
let input = result.0
let output = result.1
3。。我们选择输入的第一个和最后一个元素
let first = input.first!
let last = input.last!
是的,我也不喜欢强制拆包,但是由于
input
永远不会为空,因此这些强制拆包绝不会产生致命错误。
4。。如果现在first < last
,我们需要:
否则,我们做相反的事情。
if first < last {
return (Array(input.dropFirst()), output + [first])
} else {
return (Array(input.dropLast()), output + [last])
}
5。。最后,我们选择由reduce返回的元组的第二个元素,因为它是我们存储结果的地方。
}.1
计算时间为O(n + m),其中n为nums0.count,m为nums1.count,因为:
nums1.reversed()
这个☝️是 O(1)
all.reduce(...) { ... }
此☝️是 O(n + m),因为对 all
的每个元素都执行了关闭操作
时间复杂度为O(n)^2。请从下面的 @dfri 中查看有价值的评论。
此版本确实应该具有O(n)时间复杂度。
let merged = all.reduce(into: (all, [Int]())) { (result, elm) in
let first = result.0.first!
let last = result.0.last!
if first < last {
result.0.removeFirst()
result.1.append(first)
} else {
result.0.removeLast()
result.1.append(last)
}
}.1
答案 1 :(得分:4)
我不确定您所说的“更多'Swifty'”是什么意思,但是到了。
我会像下面这样写函数。它并不短,但更通用:您可以合并任意两个Sequence
,只要它们具有相同的Element
类型并且Element
是Comparable
:
/// Merges two sequences into one where the elements are ordered according to `Comparable`.
///
/// - Precondition: the input sequences must be sorted according to `Comparable`.
func merged<S1, S2>(_ left: S1, _ right: S2) -> [S1.Element]
where S1: Sequence, S2: Sequence, S1.Element == S2.Element, S1.Element: Comparable
{
var leftIterator = left.makeIterator()
var rightIterator = right.makeIterator()
var merged: [S1.Element] = []
merged.reserveCapacity(left.underestimatedCount + right.underestimatedCount)
var leftElement = leftIterator.next()
var rightElement = rightIterator.next()
loop: while true {
switch (leftElement, rightElement) {
case (let l?, let r?) where l <= r:
merged.append(l)
leftElement = leftIterator.next()
case (let l?, nil):
merged.append(l)
leftElement = leftIterator.next()
case (_, let r?):
merged.append(r)
rightElement = rightIterator.next()
case (nil, nil):
break loop
}
}
return merged
}
另一个有趣的增强功能是使序列变得懒惰,即定义MergedSequence
及其伴随的迭代器结构,该结构存储基本序列并按需生成下一个元素。这将类似于标准库中的许多功能,例如zip
或Sequence.joined
。 (如果不想定义新类型,也可以返回AnySequence<S1.Element>
。)
答案 2 :(得分:1)
也不确定您的定义,但是您可能会认为这更迅速:
func mergeOrdered<T: Comparable>(orderedArray1: [T], orderedArray2: [T]) -> [T] {
// Create mutable copies of the ordered arrays:
var array1 = orderedArray1
var array2 = orderedArray2
// The merged array that we'll fill up:
var mergedArray: [T] = []
while !array1.isEmpty {
guard !array2.isEmpty else {
// there is no more item in array2,
// so we can just add the remaining elements from array1:
mergedArray += array1
return mergedArray
}
var nextValue: T
if array1.first! < array2.first! {
nextValue = array1.first!
array1.removeFirst()
} else {
nextValue = array2.first!
array2.removeFirst()
}
mergedArray.append(nextValue)
}
// Add the remaining elements from array2 if any:
return mergedArray + array2
}
然后:
let merged = mergeOrdered(orderedArray1: arr1, orderedArray2: arr2)
print(merged) // prints [1, 2, 5, 7, 17, 17, 25, 29, 31, 38]
这是一个类似的想法,并且代码没有缩短很多,但是我认为“笨拙”的是,您不需要这样跟踪两个索引。
尽管这和您的实现为您提供O(n),但由于它假定两个输入数组均已排序,因此有些不安全。一个人可能会轻易地监督这一前提条件。因此,我个人仍然更喜欢
let merged = (arr1 + arr2).sorted()
但是,当然,这取决于用例。
答案 3 :(得分:1)
另一个有趣的改进是使序列变懒, 即定义一个
MergedSequence
及其伴随的迭代器结构 存储基本序列并按需生成下一个元素。
如果我们想使用某种“更快速”的方法,并且还想实现交错的惰性交错序列(基于用于元素逐项比较的<
谓词),而不是像在您的示例数组中,我们可以利用sequence(state:next:)
和辅助函数enum
,并重用Ole Begemann的答案中的一些左右switch
逻辑:
enum QueuedElement {
case none
case left(Int)
case right(Int)
}
var lazyInterleavedSeq = sequence(
state: (queued: QueuedElement.none,
leftIterator: arr1.makeIterator(),
rightIterator: arr2.makeIterator()),
next: { state -> Int? in
let leftElement: Int?
if case .left(let l) = state.queued { leftElement = l }
else { leftElement = state.leftIterator.next() }
let rightElement: Int?
if case .right(let r) = state.queued { rightElement = r }
else { rightElement = state.rightIterator.next() }
switch (leftElement, rightElement) {
case (let l?, let r?) where l <= r:
state.queued = .right(r)
return l
case (let l?, nil):
state.queued = .none
return l
case (let l, let r?):
state.queued = l.map { .left($0) } ?? .none
return r
case (_, nil):
return nil
}
})
我们可能会消耗例如用于记录:
for num in lazyInterleavedSeq { print(num) }
/* 1
2
5
7
17
17
25
29
31
38 */
或者构造一个不可变的数组:
let interleaved = Array(lazyInterleavedSeq)
// [1, 2, 5, 7, 17, 17, 25, 29, 31, 38]
答案 4 :(得分:0)
我真的很喜欢Luca Angeletti引入的功能方法。金字塔的想法也很不错,但是就我的口味而言,由于结合使用reduce
函数和数组元组,因此代码不够可读/直观。此外,金字塔概念还需要其他开发人员进一步解释。
因此,我尝试使用my original idea并从前面慢慢“切掉”这两个数组,并使其完全起作用。结果非常简单:
/// Merges two sorted arrays into a single sorted array in ascending order.
///
/// - Precondition: This function assumes that both input parameters `orderedArray1` and
/// `orderedArray2` are already sorted using the predicate `<`.
func mergeOrdered<T: Comparable>(orderedArray1: [T], orderedArray2: [T]) -> [T] {
guard let first = orderedArray1.first else {
return orderedArray2
}
guard let second = orderedArray2.first else {
return orderedArray1
}
if first < second {
return [first] + mergeOrdered(orderedArray1: Array(orderedArray1.dropFirst()),
orderedArray2: orderedArray2)
} else {
return [second] + mergeOrdered(orderedArray1: orderedArray1,
orderedArray2: Array(orderedArray2.dropFirst()))
}
}
我认为到目前为止,它比本页上建议的其他算法更容易阅读,而且根据我的判断,它甚至
(尽管应注意,Luca Angeletti's answer的注释中提到的dfri
的关注也适用于此:在每个递归步骤中实例化一个新数组,这可能在计算上是昂贵的–但是,数组实例化的总数始终为
此解决方案可以扩展为与
一起使用ℹ️在所有这些方法中, Swift标准排序算法是最快的。我使用以下两个数组对所有方法的运行时进行了测试:
let first = Array(1...9999)
let second = Array(5...500)
结果:
迭代器排序(由Ole Begemann引入):
37.110 s
功能排序(如本答案所述):
6.081 s
循环排序(在my other answer中引入):
0.695 s
快速标准排序((first + second).sorted()
)
0.013 s
当然,它总是取决于要合并的特定数组,但是从这些结果中,我认为实际上使用 (first + second).sorted()
是您可以最快,最快地完成的事情!
答案 5 :(得分:0)
这是我的意思……这是序列协议扩展,是一个通用功能,可以合并两个相同类型的序列(协议扩展),甚至可以合并任何两个Element
类型的序列。
import Cocoa
struct MergeState<T> {
var lastA: T?
var iterA: AnyIterator<T>
var lastB: T?
var iterB: AnyIterator<T>
mutating func consumeA() -> T? {
let aux = lastA
lastA = nil
return aux ?? iterA.next()
}
mutating func consumeB() -> T? {
let aux = lastB
lastB = nil
return aux ?? iterB.next()
}
}
extension Sequence where Element: Comparable {
func createMergeState(with other: Self) -> MergeState<Element> {
let iterA = AnyIterator(self.makeIterator())
let iterB = AnyIterator(other.makeIterator())
return MergeState(lastA: nil, iterA: iterA, lastB: nil, iterB: iterB)
}
func mergeSequence(with other: Self) -> UnfoldSequence<Element, MergeState<Element>> {
let state = createMergeState(with: other)
return sequence(state: state) { (state) -> Element? in
guard let valueA = state.consumeA() else {
return state.consumeB()
}
guard let valueB = state.consumeB() else {
return valueA
}
if valueA < valueB {
state.lastB = valueB
return valueA
} else {
state.lastA = valueA
return valueB
}
}
}
}
func mergeSequence<S1: Sequence, S2: Sequence>(_ seq1: S1, _ seq2: S2) -> UnfoldSequence<S1.Element, MergeState<S1.Element>> where S1.Element == S2.Element, S1.Element: Comparable {
return AnySequence(seq1).mergeSequence(with: AnySequence(seq2))
}
let a = [1, 9, 15, 55, 101]
let b = [2, 4, 6, 8]
//merge sequences of the same type
for i in a.mergeSequence(with: b) {
print("\(i)")
}
let c: IndexSet = [3, 9, 60]
print("---")
//merge any two sequences with the same Element
for i in mergeSequence(c, a) {
print("\(i)")
}
答案 6 :(得分:0)
SWIFT 5 干净的解决方案,带有 O(N),其中n是较小数组中元素的数量。使用数组切片。具有通用的最小和最大功能。
这个想法是循环遍历数量较少的数组。比较每个相对的项目,然后将其插入结果中。循环结束后,从最长的数组中追加其余项目。
func merge(a: [Int] , b: [Int]) -> [Int] {
var result: [Int] = []
let count = min(a.count, b.count)
for i in 0 ..< count {
let first = min(a[i], b[i])
let second = max(a[i], b[i])
result.append(first)
result.append(second)
}
let rest = a.count > count ? a[count...] : b[count...]
result.append(contentsOf: Array(rest))
return result
}