[swift-evolution] Dropping Comparable requirement for indices

Dave Abrahams dabrahams at apple.com
Thu Jul 7 17:58:21 CDT 2016


on Thu Jul 07 2016, Haravikk <swift-evolution at swift.org> wrote:

>> On 7 Jul 2016, at 02:41, Dave Abrahams via swift-evolution <swift-evolution at swift.org> wrote:
>> 
>> 
>> on Wed Jul 06 2016, Haravikk <swift-evolution at swift.org> wrote:
>> 
>
>>>> On 6 Jul 2016, at 03:39, Dave Abrahams via swift-evolution <swift-evolution at swift.org> wrote:
>>>> For example, with
>>>> Comparable indices, you can't build a linked list that supports
>>>> restructuring (e.g. insert, delete, splice) in O(1) without invalidating
>>>> indices... not even an unsafe linked list with reference semantics.
>>> 
>>> I think the question is why you need to retain indices in these cases?
>>> 
>>> When it comes to these operations I wonder if we might want to
>>> investigate something like a mutating iterator; you might still use an
>>> index to jump to an initial position, but then use .insert(),
>>> .remove() etc. methods of the iterator to perform modification without
>>> the need to track indices at all. 
>> 
>> There is no way, AFAIK, to implement important algorithms like rotate
>> binarySearch and several others, without having some representation of
>> position within a collection.
>> 
>>> This is essentially how you want to edit trees anyway, as indexing
>>> them isn't especially pretty, as it avoids the need to track the
>>> indices at all for these operations, and many common cases should work
>>> well when done as part of an iterator in this way.
>> 
>> I don't know what you mean by “track,” here.  We don't track the
>> indices of an array.
>
> By track I just mean store in a variable.
>
> As an example, consider removing multiple values using a closure:
>
> 	let delete:(Int) -> Bool = { ($0 % 2) == 1 } // Delete all odd numbers
> 	let filtered = myArray.filter { !delete($0) } // Produces new copy of array with elements filtered
>
> 	// In-place removal
> 	var index = myArray.startIndex, indices:[Array<Int>.Index] = []
> 	for eachElement in myArray {
> 		if delete(eachElement) { indices.append(index) }
> 		myArray.formIndex(after: &index)
> 	}
> 	for eachIndex in indices { myArray.remove(at: eachIndex }

I'm afraid that doesn't work (remove elements from [0, 1] with the
predicate {true}), and even if it did work it would take O(N^2) time and
O(N) space.  The right algorithm is O(N) time and O(1) space.

> The latter case only works with types where you know that there's a
> safe way to use the indices (removing in reverse order doesn't
> invalidate earlier indices of Array) so it's not suitable for generic
> collections. 

Exercise for the reader: write a generic one that works for any
RangeReplaceableCollection.  It should be easy.  Hint: you only want one
call removeSubrange.

> Since it requires iterating myArray anyway, then if the removals could
> be performed at the same time it would eliminate the need to store and
> then use indices at all, and would be a much better way to work with
> linked-lists, trees and so-on. So with a mutable iterator for example
> the in-place removal would look like:
>
> 	var iterator = myArray.makeMutatingIterator()
> 	while let eachElement = iterator.next() {
> 		if delete(eachElement) { iterator.remove() }
> 	}

In general, because arrays have value semantics, there's no way to
mutate the array through another object such as a mutating iterator.
And even if there was a way, it would still force the whole
array's storage to be cloned at least once, which is not something we
want to pay for.

> Maybe this isn't directly applicable to this topic, but this for
> example seems like a better solution to the linked list problems
> raised than removing Comparable as a requirement which was my intended
> point, that perhaps this isn't necessary?
>
> Otherwise I think the main issue with types that don't seem like they
> can implement Comparable is that they need a means of detecting that
> they've been invalidated; arguably a singly-linked list's indices
> should become unusable if it has been changed, which could be done for
> example by giving the list a mutationCount value that is incremented
> each time it is mutated; indices would take a copy of this value and
> if they don't match, they produce a runtime error. Like so (heavily
> abridged):
>
> 	struct LinkedListIndex<Element> : Comparable {
> 		let mutationCount:Int
> 		var position:Int, node:LinkedListNode<Element>
> 	}
> 	func == <E>(lhs:LinkedListIndex<E>, rhs:LinkedListIndex<E>) { return lhs.position == rhs.position }
> 	func < <E>(lhs:LinkedListIndex<E>, rhs:LinkedListIndex<E>) { return lhs.position < rhs.position }
>
> 	class LinkedListNode<Element> {
> 		var element:Element, next:LinkedListNode<Element>?
> 		init(_ element:Element) { self.element = element }
> 	}
>
> 	struct LinkedList<Element> : Indexable {
> 		var mutationCount:Int = 0, head:LinkedListNode<Element>?, tail:LinkedListNode<Element>?
>
> 		typealias Index = LinkedListIndex
> 		var startIndex:Index { return LinkedListIndex(mutationCount: self.mutationCount, position: 0) }
> 		func formIndex(after i:inout Index) {
> 			precondition(i.mutationCount == self.mutationCount, "Invalid index")
> 			i.position += 1
> 			i.node = i.node?.next
> 		}
> 		func index(after i:Index) -> Index { var i = i; self.formIndex(after: &i); return i }
> 		subscript(i:Index) -> Element {
> 			precondition(i.mutationCount == self.mutationCount, "Invalid index")
> 			return i.node!.element
> 		}
>
> 		mutating func append(_ element:Element) {
> 			let node = LinkedListNode(element)
> 			if self.tail == nil { self.head = node; self.tail = node }
> 			else { self.tail.next = node; self.tail = node }
> 			self.mutationCount = self.mutationCount &+ 1
> 		}
> 	}
>
> Please forgive typos/omissions, tried to keep it as short as possible. But yeah, this is essentially how I'd try to implement a linked list, while retaining the Comparable constraint.
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>

-- 
Dave



More information about the swift-evolution mailing list