From 3ef6ef4a520b62bea0aa713be96eba0eb1d490b2 Mon Sep 17 00:00:00 2001 From: Borja Arias Drake Date: Sun, 6 Aug 2017 20:14:49 +0100 Subject: [PATCH 1/3] Add conformance to Collection protocol for LinkedList class. --- .../LinkedList.playground/Contents.swift | 63 ++++++++++++++++++ Linked List/LinkedList.swift | 54 +++++++++++++++ Linked List/README.markdown | 66 +++++++++++++++++++ Linked List/Tests/LinkedListTests.swift | 8 +++ 4 files changed, 191 insertions(+) diff --git a/Linked List/LinkedList.playground/Contents.swift b/Linked List/LinkedList.playground/Contents.swift index b78ee4809..ed3de41be 100644 --- a/Linked List/LinkedList.playground/Contents.swift +++ b/Linked List/LinkedList.playground/Contents.swift @@ -317,6 +317,61 @@ extension LinkedList: ExpressibleByArrayLiteral { } } +// MARK: - Collection +extension LinkedList : Collection { + + public typealias Index = LinkedListIndex + + /// The position of the first element in a nonempty collection. + /// + /// If the collection is empty, `startIndex` is equal to `endIndex`. + /// - Complexity: O(1) + public var startIndex: Index { + get { + return LinkedListIndex(node: head, tag: 0) + } + } + + /// The collection's "past the end" position---that is, the position one + /// greater than the last valid subscript argument. + /// - Complexity: O(n), where n is the number of elements in the list. This can be improved by keeping a reference + /// to the last node in the collection. + public var endIndex: Index { + get { + if let h = self.head { + return LinkedListIndex(node: h, tag: count) + } else { + return LinkedListIndex(node: nil, tag: startIndex.tag) + } + } + } + + public subscript(position: Index) -> T { + get { + return position.node!.value + } + } + + public func index(after idx: Index) -> Index { + return LinkedListIndex(node: idx.node?.next, tag: idx.tag+1) + } +} + +// MARK: - Collection Index +/// Custom index type that contains a reference to the node at index 'tag' +public struct LinkedListIndex : Comparable +{ + fileprivate let node: LinkedList.LinkedListNode? + fileprivate let tag: Int + + public static func==(lhs: LinkedListIndex, rhs: LinkedListIndex) -> Bool { + return (lhs.tag == rhs.tag) + } + + public static func< (lhs: LinkedListIndex, rhs: LinkedListIndex) -> Bool { + return (lhs.tag < rhs.tag) + } +} //: Ok, now that the declarations are done, let's see our Linked List in action: let list = LinkedList() list.isEmpty // true @@ -392,3 +447,11 @@ let listArrayLiteral2: LinkedList = ["Swift", "Algorithm", "Club"] listArrayLiteral2.count // 3 listArrayLiteral2[0] // "Swift" listArrayLiteral2.removeLast() // "Club" + + +// Conformance to the Collection protocol +let collection: LinkedList = [1, 2, 3, 4, 5] +let index2 = collection.index(collection.startIndex, offsetBy: 2) +let value = collection[index2] // 3 + + diff --git a/Linked List/LinkedList.swift b/Linked List/LinkedList.swift index 94e978c07..f51064e01 100755 --- a/Linked List/LinkedList.swift +++ b/Linked List/LinkedList.swift @@ -244,3 +244,57 @@ extension LinkedList: ExpressibleByArrayLiteral { } } } + +extension LinkedList : Collection { + + public typealias Index = LinkedListIndex + + /// The position of the first element in a nonempty collection. + /// + /// If the collection is empty, `startIndex` is equal to `endIndex`. + /// - Complexity: O(1) + public var startIndex: Index { + get { + return LinkedListIndex(node: head, tag: 0) + } + } + + /// The collection's "past the end" position---that is, the position one + /// greater than the last valid subscript argument. + /// - Complexity: O(n), where n is the number of elements in the list. This can be improved by keeping a reference + /// to the last node in the collection. + public var endIndex: Index { + get { + if let h = self.head { + return LinkedListIndex(node: h, tag: count) + } else { + return LinkedListIndex(node: nil, tag: startIndex.tag) + } + } + } + + public subscript(position: Index) -> T { + get { + return position.node!.value + } + } + + public func index(after idx: Index) -> Index { + return LinkedListIndex(node: idx.node?.next, tag: idx.tag+1) + } +} + +/// Custom index type that contains a reference to the node at index 'tag' +public struct LinkedListIndex : Comparable +{ + fileprivate let node: LinkedList.LinkedListNode? + fileprivate let tag: Int + + public static func==(lhs: LinkedListIndex, rhs: LinkedListIndex) -> Bool { + return (lhs.tag == rhs.tag) + } + + public static func< (lhs: LinkedListIndex, rhs: LinkedListIndex) -> Bool { + return (lhs.tag < rhs.tag) + } +} diff --git a/Linked List/README.markdown b/Linked List/README.markdown index c03023c42..f1b1a2480 100644 --- a/Linked List/README.markdown +++ b/Linked List/README.markdown @@ -535,6 +535,72 @@ The big difference with the class-based version is that any modification you mak [I might fill out this section in more detail if there's a demand for it.] +## Conforming to the Collection protocol +Types that conform to the Sequence protocol, whose elements can be traversed multiple times, nondestructively, and accessed by indexed subscript should conform to the Collection protocol defined in Swift's Standard Library. + +Doing so grants access to a very large number of properties and operations that are common when dealing collections of data. In addition to this, it lets custom types follow the patterns that are common to Swift developers. + +In order to conform to this protocol, classes need to provide: + 1 `startIndex` and `endIndex` properties. + 2 Subscript access to elements as O(1). Diversions of this time complexity need to be documented. + +```swift +/// The position of the first element in a nonempty collection. +public var startIndex: Index { + get { + return LinkedListIndex(node: head, tag: 0) + } +} + +/// The collection's "past the end" position---that is, the position one +/// greater than the last valid subscript argument. +/// - Complexity: O(n), where n is the number of elements in the list. +/// This diverts from the protocol's expectation. +public var endIndex: Index { + get { + if let h = self.head { + return LinkedListIndex(node: h, tag: count) + } else { + return LinkedListIndex(node: nil, tag: startIndex.tag) + } + } +} +``` + +```swift +public subscript(position: Index) -> T { + get { + return position.node!.value + } +} +``` + +Becuase collections are responsible for managing their own indexes, the implementation below keeps a reference to a node in the list. A tag property in the index represents the position of the node in the list. + +```swift +/// Custom index type that contains a reference to the node at index 'tag' +public struct LinkedListIndex : Comparable +{ + fileprivate let node: LinkedList.LinkedListNode? + fileprivate let tag: Int + + public static func==(lhs: LinkedListIndex, rhs: LinkedListIndex) -> Bool { + return (lhs.tag == rhs.tag) + } + + public static func< (lhs: LinkedListIndex, rhs: LinkedListIndex) -> Bool { + return (lhs.tag < rhs.tag) + } +} +``` + +Finally, the linked is is able to calculate the index after a given one with the following implementation. +```swift +public func index(after idx: Index) -> Index { + return LinkedListIndex(node: idx.node?.next, tag: idx.tag+1) +} +``` + ## Some things to keep in mind Linked lists are flexible but many operations are **O(n)**. diff --git a/Linked List/Tests/LinkedListTests.swift b/Linked List/Tests/LinkedListTests.swift index 0b2b116df..bc9831a41 100755 --- a/Linked List/Tests/LinkedListTests.swift +++ b/Linked List/Tests/LinkedListTests.swift @@ -337,4 +337,12 @@ class LinkedListTest: XCTestCase { XCTAssertEqual(arrayLiteralInitExplicit.removeLast(), 3) XCTAssertEqual(arrayLiteralInitExplicit.count, 2) } + + func testConformanceToCollectionProtocol() { + let collection: LinkedList = [1, 2, 3, 4, 5] + let index2 = collection.index(collection.startIndex, offsetBy: 2) + let value = collection[index2] + + XCTAssertTrue(value == 3) + } } From f5678857ca24a9c1403a5f721d62e73ef90e64c3 Mon Sep 17 00:00:00 2001 From: Borja Arias Drake Date: Sun, 6 Aug 2017 20:23:17 +0100 Subject: [PATCH 2/3] Fix styling issue; Parentheses on new line. --- Linked List/LinkedList.playground/Contents.swift | 3 +-- Linked List/LinkedList.swift | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Linked List/LinkedList.playground/Contents.swift b/Linked List/LinkedList.playground/Contents.swift index ed3de41be..7805d4a37 100644 --- a/Linked List/LinkedList.playground/Contents.swift +++ b/Linked List/LinkedList.playground/Contents.swift @@ -359,8 +359,7 @@ extension LinkedList : Collection { // MARK: - Collection Index /// Custom index type that contains a reference to the node at index 'tag' -public struct LinkedListIndex : Comparable -{ +public struct LinkedListIndex : Comparable { fileprivate let node: LinkedList.LinkedListNode? fileprivate let tag: Int diff --git a/Linked List/LinkedList.swift b/Linked List/LinkedList.swift index f51064e01..e7e2c714e 100755 --- a/Linked List/LinkedList.swift +++ b/Linked List/LinkedList.swift @@ -285,8 +285,7 @@ extension LinkedList : Collection { } /// Custom index type that contains a reference to the node at index 'tag' -public struct LinkedListIndex : Comparable -{ +public struct LinkedListIndex : Comparable { fileprivate let node: LinkedList.LinkedListNode? fileprivate let tag: Int From 5570a711df3d1bc79ce921bd94fe2b9365e141ad Mon Sep 17 00:00:00 2001 From: Borja Arias Drake Date: Sun, 6 Aug 2017 20:32:55 +0100 Subject: [PATCH 3/3] Add two examples of Sequence methods gained through the conformance to Collection protocol. --- Linked List/LinkedList.playground/Contents.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Linked List/LinkedList.playground/Contents.swift b/Linked List/LinkedList.playground/Contents.swift index 7805d4a37..ef70c561b 100644 --- a/Linked List/LinkedList.playground/Contents.swift +++ b/Linked List/LinkedList.playground/Contents.swift @@ -453,4 +453,16 @@ let collection: LinkedList = [1, 2, 3, 4, 5] let index2 = collection.index(collection.startIndex, offsetBy: 2) let value = collection[index2] // 3 +// Iterating in a for loop, since the Sequence protocol allows this. +var sum = 0 +for element in collection { + sum += element +} +// sum is 15 + +// Another way of achieving the same result though 'reduce', another method defined in an extension of Sequence. Collections are Sequences. +let result = collection.reduce(0) {$0 + $1} // 15 + + +