Reputation: 4023
I'm looking at the Linked List implementation from here, and it shows how the class conforms to the Collection protocol:
extension LinkedList: Collection {
public typealias Index = LinkedListIndex<T>
public var startIndex: Index {
get {
return LinkedListIndex<T>(node: head, tag: 0)
}
}
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)
}
}
In order to conform to the Collection
protocol, the code provided three things: startIndex
/endIndex
, read-only subscript to get an element, and index(after:)
.
And order to make this possible, the code also provided LinkedListIndex which is a wrapper object of the linked list in question to make it conform to Comparable
:
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)
}
}
I have two questions:
Comparable
? Unlike firstIndex(of:), which requires the elements to be Equatable
, I can't seem to find anything on Apple documentation about needing to conform to Comparable
, or even Equatable
, for things like startIndex
.tag
and index.Testing
final class LinkListTest: XCTestCase {
func test_linkedList() {
let linkedList = LinkedList<Int>()
for i in stride(from: 0, to: 100, by: 10) {
linkedList.append(i)
}
let startIndex = linkedList.startIndex // startIndex has a tag of 0 because that's how it was instantiated
let expectedStartIndex = LinkedListIndex<Int>(node: linkedList.head, tag: 0)
XCTAssertEqual(startIndex, expectedStartIndex)
let endIndex = linkedList.endIndex // endIndex also has a tag of the count because that's how it was instantiated
let expectedEndIndex = LinkedListIndex<Int>(node: linkedList.last, tag: 10)
XCTAssertEqual(endIndex, expectedEndIndex)
let node = LinkedList.Node(value: 50)
let testIndex = linkedList.index(after: LinkedListIndex<Int>(node: node, tag: 50))
print("testIndex", testIndex) // LinkedListIndex<Int>(node: nil, tag: 51)
}
}
There is no iteration going through every node and associating it with LinkedListIndex
to say node C has a tag of 3, D has a tag of 4. How does index(after:)
know which node comes after LinkedListIndex<Int>(node: node, tag: 50)
?
Upvotes: 2
Views: 71
Reputation: 29918
Based on the code you show:
It's not the linked list elements which are Comparable
, but the indices. Collection
has no Comparable
requirement on its elements, but does require that its indexes be comparable so that they can be reasonably ordered:
public protocol Collection: Sequence {
// FIXME: ideally this would be in MigrationSupport.swift, but it needs
// to be on the protocol instead of as an extension
@available(*, deprecated/*, obsoleted: 5.0*/, message: "all index distances are now of type Int")
typealias IndexDistance = Int
// FIXME: Associated type inference requires this.
override associatedtype Element
/// A type that represents a position in the collection.
///
/// Valid indices consist of the position of every element and a
/// "past the end" position that's not valid for use as a subscript
/// argument.
associatedtype Index: Comparable
// ...
}
This requirement on the indices makes it possible to iterate over the Collection
one index at a time, and know where you are relative to startIndex
and endIndex
.
Note too that the syntax
public struct LinkedListIndex<T>: Comparable
does not make LinkedList
Comparable
itself, and only declares that LinkedListIndex
is Comparable
. Note that it does not reference the original list itself, but holds on to a node for constant-time access back within LinkedList
itself: but this implementation detail does not have to do with Comparable
conformance, nor does it affect it.
It appears that LinkedListIndex.tag
represents the index of the element in the list, like an array index: the first element (startIndex
) has a tag of 0
, the second element has a tag of 1
, etc.; endIndex
(the index once past the last element in the list) has an index of count
You can use an index to iterate tag
times through the list to access a specific element. This iteration isn't strictly necessary, though, because a LinkedListIndex
also contains a pointer to the node it's supposed to refer to in the list, as a shortcut.
Update with additional question:
How does index(after:) know which node comes after LinkedListIndex(node: node, tag: 50)?
This is achieved using two things:
Alongside the tag
, LinkedListIndex
values contain a pointer to the node that they reference in the original list:
public struct LinkedListIndex: Comparable { fileprivate let node: LinkedList.LinkedListNode? fileprivate let tag: Int }
This is set at the time that the index is created in:
LinkedList.startIndex
LinkedList.endIndex
LinkedList.index(after:)
The implementation of LinkedList.index(after:)
uses this node reference in its implementation:
public func index(after idx: Index) -> Index {
return LinkedListIndex<T>(node: idx.node?.next, tag: idx.tag + 1)
}
The index which comes after idx
is an index which:
idx.node
, andidx.tag
There is no further association between a node and a tag:
tag
appears to be used exclusively for Comparable
conformance for indices, as a way to avoid having to compare nodes (e.g., LinkedListIndex
trusts that if idx1.tag < idx2.tag
then presumably idx1.node
comes before idx2.node
in the list, even if this is not the case!), whilenode
is the only thing really consulted when subscripting, and when producing new indicesBecause the nodes and tags are only very loosely related in this way, this code isn't terribly robust against mismatches (e.g. if I edit the list while you hold on to an index
, the index's tag
may be out of date depending on the operation I perform!).
Upvotes: 1