Skip to content

Commit 0ced70e

Browse files
author
Matt Zanchelli
committed
Add a basic merge algorithm implementation
1 parent 288d8b0 commit 0ced70e

File tree

2 files changed

+288
-0
lines changed

2 files changed

+288
-0
lines changed

Sources/Algorithms/Merge.swift

+230
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift Algorithms open source project
4+
//
5+
// Copyright (c) 2022 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
/// A merge of two sequences with the same element type both pre-sorted by the
13+
/// same predicate.
14+
public struct Merge2Sequence<Base1: Sequence, Base2: Sequence>
15+
where Base1.Element == Base2.Element
16+
{
17+
/// The first sequence in this merged sequence
18+
@usableFromInline
19+
internal let base1: Base1
20+
21+
/// The second sequence in this merged sequence
22+
@usableFromInline
23+
internal let base2: Base2
24+
25+
/// A predicate that returns `true` if its first argument should be ordered
26+
/// before its second argument; otherwise, `false`.
27+
@usableFromInline
28+
internal let areInIncreasingOrder: (Base2.Element, Base1.Element) -> Bool
29+
30+
@inlinable
31+
internal init(
32+
base1: Base1,
33+
base2: Base2,
34+
areInIncreasingOrder: @escaping (Base2.Element, Base1.Element) -> Bool
35+
) {
36+
self.base1 = base1
37+
self.base2 = base2
38+
self.areInIncreasingOrder = areInIncreasingOrder
39+
}
40+
}
41+
42+
extension Merge2Sequence: Sequence {
43+
/// The iterator for a `Merge2Sequence` instance
44+
public struct Iterator: IteratorProtocol {
45+
@usableFromInline
46+
internal var iterator1: Base1.Iterator
47+
48+
@usableFromInline
49+
internal var iterator2: Base2.Iterator
50+
51+
@usableFromInline
52+
internal let areInIncreasingOrder: (Base2.Element, Base1.Element) -> Bool
53+
54+
@usableFromInline
55+
internal enum IterationState {
56+
case iterating
57+
case finished1
58+
case finished2
59+
case finished
60+
}
61+
62+
@usableFromInline
63+
internal var iterationState: IterationState = .iterating
64+
65+
@usableFromInline
66+
internal var previousElement1: Base1.Element? = nil
67+
68+
@usableFromInline
69+
internal var previousElement2: Base2.Element? = nil
70+
71+
@inlinable
72+
internal init(_ mergedSequence: Merge2Sequence) {
73+
iterator1 = mergedSequence.base1.makeIterator()
74+
iterator2 = mergedSequence.base2.makeIterator()
75+
areInIncreasingOrder = mergedSequence.areInIncreasingOrder
76+
}
77+
78+
@inlinable
79+
public mutating func next() -> Base1.Element? {
80+
switch iterationState {
81+
case .iterating:
82+
switch (previousElement1 ?? iterator1.next(), previousElement2 ?? iterator2.next()) {
83+
case (.some(let element1), .some(let element2)):
84+
if areInIncreasingOrder(element2, element1) {
85+
previousElement1 = element1
86+
previousElement2 = nil
87+
return element2
88+
} else {
89+
previousElement1 = nil
90+
previousElement2 = element2
91+
return element1
92+
}
93+
94+
case (nil, .some(let element2)):
95+
iterationState = .finished1
96+
return element2
97+
98+
case (.some(let element1), nil):
99+
iterationState = .finished2
100+
return element1
101+
102+
case (nil, nil):
103+
iterationState = .finished
104+
return nil
105+
}
106+
107+
case .finished1:
108+
let element = iterator2.next()
109+
if element == nil {
110+
iterationState = .finished
111+
}
112+
return element
113+
114+
case .finished2:
115+
let element = iterator1.next()
116+
if element == nil {
117+
iterationState = .finished
118+
}
119+
return element
120+
121+
case .finished:
122+
return nil
123+
}
124+
}
125+
}
126+
127+
@inlinable
128+
public func makeIterator() -> Iterator {
129+
Iterator(self)
130+
}
131+
}
132+
133+
//===----------------------------------------------------------------------===//
134+
// merge(_:_:areInIncreasingOrder:)
135+
//===----------------------------------------------------------------------===//
136+
137+
/// Returns a new sequence that iterates over the two given sequences,
138+
/// alternating between elements of the two sequences, returning the lesser of
139+
/// the two elements, as defined by a predicate, `areInIncreasingOrder`.
140+
///
141+
/// You can pass any two sequences or collections that have the same element
142+
/// type as this sequence and are pre-sorted by the given predicate. This
143+
/// example merges two sequences of `Int`s:
144+
///
145+
/// let evens = stride(from: 0, to: 10, by: 2)
146+
/// let odds = stride(from: 1, to: 10, by: 2)
147+
/// for num in merge(evens, odds, by: <) {
148+
/// print(num)
149+
/// }
150+
/// // 0
151+
/// // 1
152+
/// // 2
153+
/// // 3
154+
/// // 4
155+
/// // 5
156+
/// // 6
157+
/// // 7
158+
/// // 8
159+
/// // 9
160+
///
161+
/// - Parameters:
162+
/// - s1: The first sequence.
163+
/// - s2: The second sequence.
164+
/// - areInIncreasingOrder: A closure that takes an element of `s2` and `s1`,
165+
/// respectively, and returns whether the first element should appear before
166+
/// the second.
167+
/// - Returns: A sequence that iterates first over the elements of `s1` and `s2`
168+
/// in a sorted order
169+
///
170+
/// - Complexity: O(1)
171+
@inlinable
172+
public func merge<S1: Sequence, S2: Sequence>(
173+
_ s1: S1,
174+
_ s2: S2,
175+
areInIncreasingOrder: @escaping (S1.Element, S2.Element) -> Bool
176+
) -> Merge2Sequence<S1, S2> where S1.Element == S2.Element {
177+
Merge2Sequence(
178+
base1: s1,
179+
base2: s2,
180+
areInIncreasingOrder: areInIncreasingOrder
181+
)
182+
}
183+
184+
//===----------------------------------------------------------------------===//
185+
// merge(_:_:)
186+
//===----------------------------------------------------------------------===//
187+
188+
/// Returns a new sequence that iterates over the two given sequences,
189+
/// alternating between elements of the two sequences, returning the lesser of
190+
/// the two elements, as defined by the elements `Comparable` implementation.
191+
///
192+
/// You can pass any two sequences or collections that have the same element
193+
/// type as this sequence and are `Comparable`. This example merges two
194+
/// sequences of `Int`s:
195+
///
196+
/// let evens = stride(from: 0, to: 10, by: 2)
197+
/// let odds = stride(from: 1, to: 10, by: 2)
198+
/// for num in merge(evens, odds) {
199+
/// print(num)
200+
/// }
201+
/// // 0
202+
/// // 1
203+
/// // 2
204+
/// // 3
205+
/// // 4
206+
/// // 5
207+
/// // 6
208+
/// // 7
209+
/// // 8
210+
/// // 9
211+
///
212+
/// - Parameters:
213+
/// - s1: The first sequence.
214+
/// - s2: The second sequence.
215+
/// - Returns: A sequence that iterates first over the elements of `s1` and `s2`
216+
/// in a sorted order
217+
///
218+
/// - Complexity: O(1)
219+
@inlinable
220+
public func merge<S1: Sequence, S2: Sequence>(
221+
_ s1: S1,
222+
_ s2: S2
223+
) -> Merge2Sequence<S1, S2>
224+
where S1.Element == S2.Element, S1.Element: Comparable {
225+
Merge2Sequence(
226+
base1: s1,
227+
base2: s2,
228+
areInIncreasingOrder: <
229+
)
230+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift Algorithms open source project
4+
//
5+
// Copyright (c) 2022 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
import XCTest
13+
@testable import Algorithms
14+
15+
final class MergeTests: XCTestCase {
16+
func testMergeArrays() {
17+
let evens = [0, 2, 4, 6, 8]
18+
let odds = [1, 3, 5, 7, 9]
19+
let output = merge(evens, odds)
20+
XCTAssertEqualSequences(output, 0...9)
21+
}
22+
23+
func testMergeSequences() {
24+
let evens = stride(from: 0, to: 10, by: 2)
25+
let odds = stride(from: 1, to: 10, by: 2)
26+
let output = merge(evens, odds)
27+
XCTAssertEqualSequences(output, 0...9)
28+
}
29+
30+
func testMergeMixedSequences() {
31+
let evens = [0, 2, 4, 6, 8]
32+
let odds = stride(from: 1, to: 10, by: 2)
33+
let output = merge(evens, odds)
34+
XCTAssertEqualSequences(output, 0...9)
35+
}
36+
37+
func testMergeSequencesWithEqualElements() {
38+
let a = [1, 2, 3, 4, 5]
39+
let b = [1, 2, 3, 4, 5]
40+
let output = merge(a, b)
41+
XCTAssertEqualSequences(output, [1, 1, 2, 2, 3, 3, 4, 4, 5, 5])
42+
}
43+
44+
func testMerge3Sequences() {
45+
let a = [0, 3, 6, 9]
46+
let b = [1, 5, 8, 10]
47+
let c = [2, 4, 7, 11]
48+
let output = merge(merge(a, b), c)
49+
XCTAssertEqualSequences(output, 0...11)
50+
}
51+
52+
func testNonDefaultSortOrder() {
53+
let evens = [8, 6, 4, 2, 0]
54+
let odds = stride(from: 9, to: 0, by: -2)
55+
let output = merge(evens, odds, areInIncreasingOrder: >)
56+
XCTAssertEqualSequences(output, (0...9).reversed())
57+
}
58+
}

0 commit comments

Comments
 (0)