Skip to content

Commit abbe07a

Browse files
authored
Merge pull request kodecocodes#583 from newboadki/linkedListConformanceToCollectionProtocol
Linked list conformance to collection protocol
2 parents 841b443 + 5570a71 commit abbe07a

File tree

4 files changed

+201
-0
lines changed

4 files changed

+201
-0
lines changed

Linked List/LinkedList.playground/Contents.swift

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,60 @@ extension LinkedList: ExpressibleByArrayLiteral {
301301
}
302302
}
303303

304+
// MARK: - Collection
305+
extension LinkedList : Collection {
306+
307+
public typealias Index = LinkedListIndex<T>
308+
309+
/// The position of the first element in a nonempty collection.
310+
///
311+
/// If the collection is empty, `startIndex` is equal to `endIndex`.
312+
/// - Complexity: O(1)
313+
public var startIndex: Index {
314+
get {
315+
return LinkedListIndex<T>(node: head, tag: 0)
316+
}
317+
}
318+
319+
/// The collection's "past the end" position---that is, the position one
320+
/// greater than the last valid subscript argument.
321+
/// - Complexity: O(n), where n is the number of elements in the list. This can be improved by keeping a reference
322+
/// to the last node in the collection.
323+
public var endIndex: Index {
324+
get {
325+
if let h = self.head {
326+
return LinkedListIndex<T>(node: h, tag: count)
327+
} else {
328+
return LinkedListIndex<T>(node: nil, tag: startIndex.tag)
329+
}
330+
}
331+
}
332+
333+
public subscript(position: Index) -> T {
334+
get {
335+
return position.node!.value
336+
}
337+
}
338+
339+
public func index(after idx: Index) -> Index {
340+
return LinkedListIndex<T>(node: idx.node?.next, tag: idx.tag+1)
341+
}
342+
}
343+
344+
// MARK: - Collection Index
345+
/// Custom index type that contains a reference to the node at index 'tag'
346+
public struct LinkedListIndex<T> : Comparable {
347+
fileprivate let node: LinkedList<T>.LinkedListNode<T>?
348+
fileprivate let tag: Int
349+
350+
public static func==<T>(lhs: LinkedListIndex<T>, rhs: LinkedListIndex<T>) -> Bool {
351+
return (lhs.tag == rhs.tag)
352+
}
353+
354+
public static func< <T>(lhs: LinkedListIndex<T>, rhs: LinkedListIndex<T>) -> Bool {
355+
return (lhs.tag < rhs.tag)
356+
}
357+
}
304358
//: Ok, now that the declarations are done, let's see our Linked List in action:
305359
let list = LinkedList<String>()
306360
list.isEmpty // true
@@ -400,3 +454,23 @@ let listArrayLiteral2: LinkedList = ["Swift", "Algorithm", "Club"]
400454
listArrayLiteral2.count // 3
401455
listArrayLiteral2[0] // "Swift"
402456
listArrayLiteral2.removeLast() // "Club"
457+
458+
459+
// Conformance to the Collection protocol
460+
let collection: LinkedList<Int> = [1, 2, 3, 4, 5]
461+
let index2 = collection.index(collection.startIndex, offsetBy: 2)
462+
let value = collection[index2] // 3
463+
464+
// Iterating in a for loop, since the Sequence protocol allows this.
465+
var sum = 0
466+
for element in collection {
467+
sum += element
468+
}
469+
// sum is 15
470+
471+
// Another way of achieving the same result though 'reduce', another method defined in an extension of Sequence. Collections are Sequences.
472+
let result = collection.reduce(0) {$0 + $1} // 15
473+
474+
475+
476+

Linked List/LinkedList.swift

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,3 +231,56 @@ extension LinkedList: ExpressibleByArrayLiteral {
231231
}
232232
}
233233
}
234+
235+
extension LinkedList : Collection {
236+
237+
public typealias Index = LinkedListIndex<T>
238+
239+
/// The position of the first element in a nonempty collection.
240+
///
241+
/// If the collection is empty, `startIndex` is equal to `endIndex`.
242+
/// - Complexity: O(1)
243+
public var startIndex: Index {
244+
get {
245+
return LinkedListIndex<T>(node: head, tag: 0)
246+
}
247+
}
248+
249+
/// The collection's "past the end" position---that is, the position one
250+
/// greater than the last valid subscript argument.
251+
/// - Complexity: O(n), where n is the number of elements in the list. This can be improved by keeping a reference
252+
/// to the last node in the collection.
253+
public var endIndex: Index {
254+
get {
255+
if let h = self.head {
256+
return LinkedListIndex<T>(node: h, tag: count)
257+
} else {
258+
return LinkedListIndex<T>(node: nil, tag: startIndex.tag)
259+
}
260+
}
261+
}
262+
263+
public subscript(position: Index) -> T {
264+
get {
265+
return position.node!.value
266+
}
267+
}
268+
269+
public func index(after idx: Index) -> Index {
270+
return LinkedListIndex<T>(node: idx.node?.next, tag: idx.tag+1)
271+
}
272+
}
273+
274+
/// Custom index type that contains a reference to the node at index 'tag'
275+
public struct LinkedListIndex<T> : Comparable {
276+
fileprivate let node: LinkedList<T>.LinkedListNode<T>?
277+
fileprivate let tag: Int
278+
279+
public static func==<T>(lhs: LinkedListIndex<T>, rhs: LinkedListIndex<T>) -> Bool {
280+
return (lhs.tag == rhs.tag)
281+
}
282+
283+
public static func< <T>(lhs: LinkedListIndex<T>, rhs: LinkedListIndex<T>) -> Bool {
284+
return (lhs.tag < rhs.tag)
285+
}
286+
}

Linked List/README.markdown

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,72 @@ The big difference with the class-based version is that any modification you mak
559559

560560
[I might fill out this section in more detail if there's a demand for it.]
561561

562+
## Conforming to the Collection protocol
563+
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.
564+
565+
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.
566+
567+
In order to conform to this protocol, classes need to provide:
568+
1 `startIndex` and `endIndex` properties.
569+
2 Subscript access to elements as O(1). Diversions of this time complexity need to be documented.
570+
571+
```swift
572+
/// The position of the first element in a nonempty collection.
573+
public var startIndex: Index {
574+
get {
575+
return LinkedListIndex<T>(node: head, tag: 0)
576+
}
577+
}
578+
579+
/// The collection's "past the end" position---that is, the position one
580+
/// greater than the last valid subscript argument.
581+
/// - Complexity: O(n), where n is the number of elements in the list.
582+
/// This diverts from the protocol's expectation.
583+
public var endIndex: Index {
584+
get {
585+
if let h = self.head {
586+
return LinkedListIndex<T>(node: h, tag: count)
587+
} else {
588+
return LinkedListIndex<T>(node: nil, tag: startIndex.tag)
589+
}
590+
}
591+
}
592+
```
593+
594+
```swift
595+
public subscript(position: Index) -> T {
596+
get {
597+
return position.node!.value
598+
}
599+
}
600+
```
601+
602+
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.
603+
604+
```swift
605+
/// Custom index type that contains a reference to the node at index 'tag'
606+
public struct LinkedListIndex<T> : Comparable
607+
{
608+
fileprivate let node: LinkedList<T>.LinkedListNode<T>?
609+
fileprivate let tag: Int
610+
611+
public static func==<T>(lhs: LinkedListIndex<T>, rhs: LinkedListIndex<T>) -> Bool {
612+
return (lhs.tag == rhs.tag)
613+
}
614+
615+
public static func< <T>(lhs: LinkedListIndex<T>, rhs: LinkedListIndex<T>) -> Bool {
616+
return (lhs.tag < rhs.tag)
617+
}
618+
}
619+
```
620+
621+
Finally, the linked is is able to calculate the index after a given one with the following implementation.
622+
```swift
623+
public func index(after idx: Index) -> Index {
624+
return LinkedListIndex<T>(node: idx.node?.next, tag: idx.tag+1)
625+
}
626+
```
627+
562628
## Some things to keep in mind
563629

564630
Linked lists are flexible but many operations are **O(n)**.

Linked List/Tests/LinkedListTests.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,4 +328,12 @@ class LinkedListTest: XCTestCase {
328328
XCTAssertEqual(arrayLiteralInitExplicit.removeLast(), 3)
329329
XCTAssertEqual(arrayLiteralInitExplicit.count, 2)
330330
}
331+
332+
func testConformanceToCollectionProtocol() {
333+
let collection: LinkedList<Int> = [1, 2, 3, 4, 5]
334+
let index2 = collection.index(collection.startIndex, offsetBy: 2)
335+
let value = collection[index2]
336+
337+
XCTAssertTrue(value == 3)
338+
}
331339
}

0 commit comments

Comments
 (0)