diff --git a/Guides/AdjacentPairs.md b/Guides/AdjacentPairs.md new file mode 100644 index 00000000..288d5849 --- /dev/null +++ b/Guides/AdjacentPairs.md @@ -0,0 +1,48 @@ +# AdjacentPairs + +[[Source](https://github.com/apple/swift-algorithms/blob/main/Sources/Algorithms/AdjacentPairs.swift) | + [Tests](https://github.com/apple/swift-algorithms/blob/main/Tests/SwiftAlgorithmsTests/AdjacentPairs.swift)] + +Considers each pair of elements of a collection iteratively. + +This operation is available throught the `adjacentPairs` property on any collection or +lazy collection. + +```swift +let pairs = [10, 20, 30, 40].adjacentPairs +// Array(pairs) == [(10, 20), (20, 30), (30, 40)] +``` + +## Detailed Design + +The `adjacentPairs` property is added as an extension method on the `Collection` +and `LazyCollectionProtocol`: + +```swift +extension LazyCollectionProtocol { + public typealias AdjacentPair = (leading: Base.Element, trailing: Base.Element) + public var adjacentPairs: LazyAdjacentPairs +} + +extension Collection { + public typealias AdjacentPair = LazyAdjacentPairs.AdjacentPair + public var adjacentPairs: [AdjacentPair] +} + +``` + +The resulting `LazyAdjacentPairs` conforms to `LazyCollectionProtocol` with +conditional conformance to the `BidirectionalCollection`, and +`RandomAccessCollection` protocols when the base type conforms. + +`Collection` does not return the `LazyAdjacentPairs` collection, but instead returns +an Array of the `AdjacentPair` tuples instead. + + +## Naming + +The lower-indexed element of each pair is called the leading element, whereas +the higher-indexed element is referred to as the trailing element. +This is consistent with the naming of leading & trailing alignments in SwiftUI. + +Alternatives considered for "leading" & "trailing" were "lower" & "upper", respectively. diff --git a/Sources/Algorithms/AdjacentPairs.swift b/Sources/Algorithms/AdjacentPairs.swift new file mode 100644 index 00000000..59f921f0 --- /dev/null +++ b/Sources/Algorithms/AdjacentPairs.swift @@ -0,0 +1,73 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Algorithms open source project +// +// Copyright (c) 2020 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +public struct LazyAdjacentPairs { + /// The leading element is the lower indexed element of the pair. + public typealias AdjacentPair = (leading: Base.Element, trailing: Base.Element) + + /// The collection of which to consider each pair of adjacent elements. + public let base: Base + +} + +extension LazyAdjacentPairs: LazyCollectionProtocol { + public typealias Index = Base.Index + + public var startIndex: Index { + base.startIndex + } + + public var endIndex: Index { + base.index(base.endIndex, offsetBy: -1) + } + + public func index(after i: Index) -> Index { + base.index(after: i) + } + + public subscript(position: Index) -> AdjacentPair { + (leading: base[position], trailing: base[base.index(after: position)]) + } + +} + +extension LazyAdjacentPairs: BidirectionalCollection where Base: BidirectionalCollection { + public func index(before i: Index) -> Index { + base.index(before: i) + } + +} + +extension LazyAdjacentPairs: RandomAccessCollection where Base: RandomAccessCollection {} +extension LazyAdjacentPairs: Equatable where Base: Equatable {} +extension LazyAdjacentPairs: Hashable where Base: Hashable {} + +//===----------------------------------------------------------------------===// +// adjacentPairs +//===----------------------------------------------------------------------===// + +extension LazyCollectionProtocol { + public var adjacentPairs: LazyAdjacentPairs { + LazyAdjacentPairs(base: self) + } + +} + +extension Collection { + public typealias AdjacentPair = LazyAdjacentPairs.AdjacentPair + + public var adjacentPairs: [AdjacentPair] { + // not lazy, as this is computed immediately + LazyAdjacentPairs(base: self) + .map { $0 } + } + +} diff --git a/Tests/SwiftAlgorithmsTests/AdjacentPairsTests.swift b/Tests/SwiftAlgorithmsTests/AdjacentPairsTests.swift new file mode 100644 index 00000000..f98d6083 --- /dev/null +++ b/Tests/SwiftAlgorithmsTests/AdjacentPairsTests.swift @@ -0,0 +1,36 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Algorithms open source project +// +// Copyright (c) 2020 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import XCTest +import Algorithms + +final class AdjacentPairsTests: XCTestCase { + func testAdjacentPairs() { + let list = [10, 20, 30, 40, 50] + let pairs = list.adjacentPairs + let expectedResult = [(10, 20), (20, 30), (30, 40), (40, 50)] + + XCTAssertEqual(pairs.first?.leading, 10) + XCTAssertEqual(pairs.last?.trailing, 50) + XCTAssertEqual(pairs.count, expectedResult.count) + } + + func testLazyAdjacentPairs() { + let list = "ABCDEF".unicodeScalars.lazy + let lazyPairs = list.adjacentPairs + let expectedResult = [("A", "B"), ("B", "C"), ("C", "D"), ("D", "E"), ("E", "F")] + + XCTAssertEqual(lazyPairs.first?.trailing, "B") + XCTAssertEqual(lazyPairs.last?.leading, "E") + XCTAssertEqual(lazyPairs.count, expectedResult.count) + } + +}