Skip to content

Commit 3ef6ef4

Browse files
committed
Add conformance to Collection protocol for LinkedList class.
1 parent 3b79812 commit 3ef6ef4

File tree

4 files changed

+191
-0
lines changed

4 files changed

+191
-0
lines changed

Linked List/LinkedList.playground/Contents.swift

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,61 @@ extension LinkedList: ExpressibleByArrayLiteral {
317317
}
318318
}
319319

320+
// MARK: - Collection
321+
extension LinkedList : Collection {
322+
323+
public typealias Index = LinkedListIndex<T>
324+
325+
/// The position of the first element in a nonempty collection.
326+
///
327+
/// If the collection is empty, `startIndex` is equal to `endIndex`.
328+
/// - Complexity: O(1)
329+
public var startIndex: Index {
330+
get {
331+
return LinkedListIndex<T>(node: head, tag: 0)
332+
}
333+
}
334+
335+
/// The collection's "past the end" position---that is, the position one
336+
/// greater than the last valid subscript argument.
337+
/// - Complexity: O(n), where n is the number of elements in the list. This can be improved by keeping a reference
338+
/// to the last node in the collection.
339+
public var endIndex: Index {
340+
get {
341+
if let h = self.head {
342+
return LinkedListIndex<T>(node: h, tag: count)
343+
} else {
344+
return LinkedListIndex<T>(node: nil, tag: startIndex.tag)
345+
}
346+
}
347+
}
348+
349+
public subscript(position: Index) -> T {
350+
get {
351+
return position.node!.value
352+
}
353+
}
354+
355+
public func index(after idx: Index) -> Index {
356+
return LinkedListIndex<T>(node: idx.node?.next, tag: idx.tag+1)
357+
}
358+
}
359+
360+
// MARK: - Collection Index
361+
/// Custom index type that contains a reference to the node at index 'tag'
362+
public struct LinkedListIndex<T> : Comparable
363+
{
364+
fileprivate let node: LinkedList<T>.LinkedListNode<T>?
365+
fileprivate let tag: Int
366+
367+
public static func==<T>(lhs: LinkedListIndex<T>, rhs: LinkedListIndex<T>) -> Bool {
368+
return (lhs.tag == rhs.tag)
369+
}
370+
371+
public static func< <T>(lhs: LinkedListIndex<T>, rhs: LinkedListIndex<T>) -> Bool {
372+
return (lhs.tag < rhs.tag)
373+
}
374+
}
320375
//: Ok, now that the declarations are done, let's see our Linked List in action:
321376
let list = LinkedList<String>()
322377
list.isEmpty // true
@@ -392,3 +447,11 @@ let listArrayLiteral2: LinkedList = ["Swift", "Algorithm", "Club"]
392447
listArrayLiteral2.count // 3
393448
listArrayLiteral2[0] // "Swift"
394449
listArrayLiteral2.removeLast() // "Club"
450+
451+
452+
// Conformance to the Collection protocol
453+
let collection: LinkedList<Int> = [1, 2, 3, 4, 5]
454+
let index2 = collection.index(collection.startIndex, offsetBy: 2)
455+
let value = collection[index2] // 3
456+
457+

Linked List/LinkedList.swift

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

Linked List/README.markdown

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

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

538+
## Conforming to the Collection protocol
539+
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.
540+
541+
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.
542+
543+
In order to conform to this protocol, classes need to provide:
544+
1 `startIndex` and `endIndex` properties.
545+
2 Subscript access to elements as O(1). Diversions of this time complexity need to be documented.
546+
547+
```swift
548+
/// The position of the first element in a nonempty collection.
549+
public var startIndex: Index {
550+
get {
551+
return LinkedListIndex<T>(node: head, tag: 0)
552+
}
553+
}
554+
555+
/// The collection's "past the end" position---that is, the position one
556+
/// greater than the last valid subscript argument.
557+
/// - Complexity: O(n), where n is the number of elements in the list.
558+
/// This diverts from the protocol's expectation.
559+
public var endIndex: Index {
560+
get {
561+
if let h = self.head {
562+
return LinkedListIndex<T>(node: h, tag: count)
563+
} else {
564+
return LinkedListIndex<T>(node: nil, tag: startIndex.tag)
565+
}
566+
}
567+
}
568+
```
569+
570+
```swift
571+
public subscript(position: Index) -> T {
572+
get {
573+
return position.node!.value
574+
}
575+
}
576+
```
577+
578+
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.
579+
580+
```swift
581+
/// Custom index type that contains a reference to the node at index 'tag'
582+
public struct LinkedListIndex<T> : Comparable
583+
{
584+
fileprivate let node: LinkedList<T>.LinkedListNode<T>?
585+
fileprivate let tag: Int
586+
587+
public static func==<T>(lhs: LinkedListIndex<T>, rhs: LinkedListIndex<T>) -> Bool {
588+
return (lhs.tag == rhs.tag)
589+
}
590+
591+
public static func< <T>(lhs: LinkedListIndex<T>, rhs: LinkedListIndex<T>) -> Bool {
592+
return (lhs.tag < rhs.tag)
593+
}
594+
}
595+
```
596+
597+
Finally, the linked is is able to calculate the index after a given one with the following implementation.
598+
```swift
599+
public func index(after idx: Index) -> Index {
600+
return LinkedListIndex<T>(node: idx.node?.next, tag: idx.tag+1)
601+
}
602+
```
603+
538604
## Some things to keep in mind
539605

540606
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
@@ -337,4 +337,12 @@ class LinkedListTest: XCTestCase {
337337
XCTAssertEqual(arrayLiteralInitExplicit.removeLast(), 3)
338338
XCTAssertEqual(arrayLiteralInitExplicit.count, 2)
339339
}
340+
341+
func testConformanceToCollectionProtocol() {
342+
let collection: LinkedList<Int> = [1, 2, 3, 4, 5]
343+
let index2 = collection.index(collection.startIndex, offsetBy: 2)
344+
let value = collection[index2]
345+
346+
XCTAssertTrue(value == 3)
347+
}
340348
}

0 commit comments

Comments
 (0)