Skip to content

Commit

Permalink
Merge pull request kodecocodes#583 from newboadki/linkedListConforman…
Browse files Browse the repository at this point in the history
…ceToCollectionProtocol

Linked list conformance to collection protocol
  • Loading branch information
kelvinlauKL authored Dec 20, 2017
2 parents 841b443 + 5570a71 commit abbe07a
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 0 deletions.
74 changes: 74 additions & 0 deletions Linked List/LinkedList.playground/Contents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,60 @@ extension LinkedList: ExpressibleByArrayLiteral {
}
}

// MARK: - Collection
extension LinkedList : Collection {

public typealias Index = LinkedListIndex<T>

/// 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<T>(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<T>(node: h, tag: count)
} else {
return LinkedListIndex<T>(node: nil, tag: startIndex.tag)
}
}
}

public subscript(position: Index) -> T {
get {
return position.node!.value
}
}

public func index(after idx: Index) -> Index {
return LinkedListIndex<T>(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<T> : Comparable {
fileprivate let node: LinkedList<T>.LinkedListNode<T>?
fileprivate let tag: Int

public static func==<T>(lhs: LinkedListIndex<T>, rhs: LinkedListIndex<T>) -> Bool {
return (lhs.tag == rhs.tag)
}

public static func< <T>(lhs: LinkedListIndex<T>, rhs: LinkedListIndex<T>) -> Bool {
return (lhs.tag < rhs.tag)
}
}
//: Ok, now that the declarations are done, let's see our Linked List in action:
let list = LinkedList<String>()
list.isEmpty // true
Expand Down Expand Up @@ -400,3 +454,23 @@ let listArrayLiteral2: LinkedList = ["Swift", "Algorithm", "Club"]
listArrayLiteral2.count // 3
listArrayLiteral2[0] // "Swift"
listArrayLiteral2.removeLast() // "Club"


// Conformance to the Collection protocol
let collection: LinkedList<Int> = [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




53 changes: 53 additions & 0 deletions Linked List/LinkedList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -231,3 +231,56 @@ extension LinkedList: ExpressibleByArrayLiteral {
}
}
}

extension LinkedList : Collection {

public typealias Index = LinkedListIndex<T>

/// 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<T>(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<T>(node: h, tag: count)
} else {
return LinkedListIndex<T>(node: nil, tag: startIndex.tag)
}
}
}

public subscript(position: Index) -> T {
get {
return position.node!.value
}
}

public func index(after idx: Index) -> Index {
return LinkedListIndex<T>(node: idx.node?.next, tag: idx.tag+1)
}
}

/// Custom index type that contains a reference to the node at index 'tag'
public struct LinkedListIndex<T> : Comparable {
fileprivate let node: LinkedList<T>.LinkedListNode<T>?
fileprivate let tag: Int

public static func==<T>(lhs: LinkedListIndex<T>, rhs: LinkedListIndex<T>) -> Bool {
return (lhs.tag == rhs.tag)
}

public static func< <T>(lhs: LinkedListIndex<T>, rhs: LinkedListIndex<T>) -> Bool {
return (lhs.tag < rhs.tag)
}
}
66 changes: 66 additions & 0 deletions Linked List/README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,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<T>(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<T>(node: h, tag: count)
} else {
return LinkedListIndex<T>(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<T> : Comparable
{
fileprivate let node: LinkedList<T>.LinkedListNode<T>?
fileprivate let tag: Int

public static func==<T>(lhs: LinkedListIndex<T>, rhs: LinkedListIndex<T>) -> Bool {
return (lhs.tag == rhs.tag)
}

public static func< <T>(lhs: LinkedListIndex<T>, rhs: LinkedListIndex<T>) -> 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<T>(node: idx.node?.next, tag: idx.tag+1)
}
```

## Some things to keep in mind

Linked lists are flexible but many operations are **O(n)**.
Expand Down
8 changes: 8 additions & 0 deletions Linked List/Tests/LinkedListTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -328,4 +328,12 @@ class LinkedListTest: XCTestCase {
XCTAssertEqual(arrayLiteralInitExplicit.removeLast(), 3)
XCTAssertEqual(arrayLiteralInitExplicit.count, 2)
}

func testConformanceToCollectionProtocol() {
let collection: LinkedList<Int> = [1, 2, 3, 4, 5]
let index2 = collection.index(collection.startIndex, offsetBy: 2)
let value = collection[index2]

XCTAssertTrue(value == 3)
}
}

0 comments on commit abbe07a

Please sign in to comment.