promacuser promacuser - 4 months ago 13
Swift Question

Adopting CollectionType (Collection) in Swift

I'm writing a graphics library to display data in a graph. Since most of the projects I do tend to have a large learning component in them, I decided to create a generically typed struct to manage my data set

DataSet<T: Plottable>
(note here that
Plottable
is also
Comparable
).

In trying to conform to
MutableCollectionType
, I've run across an error. I'd like to use the default implementation of
sort()
, but the compiler is giving the following error when trying to use the sorting function.


Ambiguous reference to member 'sort()'


Here's a code example:

var data = DataSet<Int>(elements: [1,2,3,4])
data.sort() //Ambiguous reference to member 'sort()'


The compiler suggests two candidates, but will not actually display them to me. Note that the compiler error goes away if I explicitly implement
sort()
on my struct.

But the bigger question remains for me. What am I not seeing that I expect the default implementation to be providing? Or am I running across a bug in Swift 3 (this rarely is the case... usually I have overlooked something).

Here's the balance of the struct:

struct DataSet<T: Plottable>: MutableCollection, BidirectionalCollection {
typealias Element = T
typealias Iterator = DataSetIterator<T>
typealias Index = Int

/**
The list of elements in the data set. Private.
*/
private var elements: [Element] = []

/**
Initalize the data set with an array of data.
*/
init(elements data: [T] = []) {
self.elements = data
}

//MARK: Sequence Protocol
func makeIterator() -> DataSetIterator<T> {
return DataSetIterator(self)
}

//MARK: Collection Protocol
subscript(_ index:DataSet<T>.Index) -> DataSet<T>.Iterator.Element {
set {
elements[index] = newValue
}
get {
return elements[index]
}
}

subscript(_ inRange:Range<DataSet<T>.Index>) -> DataSet<T> {
set {
elements.replaceSubrange(inRange, with: newValue)
}
get {
return DataSet<T>(elements: Array(elements[inRange]))
}
}

//required index for MutableCollection and BidirectionalCollection
var endIndex: Int {
return elements.count
}
var startIndex: Int {
return 0
}
func index(after i: Int) -> Int {
return i+1
}
func index(before i: Int) -> Int {
return i-1
}

mutating func append(_ newElement: T) {
elements.append(newElement)
}

// /**
// Sorts the elements of the DataSet from lowest value to highest value.
// Commented because I'd like to use the default implementation.
// - note: This is equivalent to calling `sort(by: { $0 < $1 })`
// */
// mutating func sort() {
// self.sort(by: { $0 < $1 })
// }
//
// /**
// Sorts the elements of the DataSet by an abritrary block.
// */
// mutating func sort(by areInIncreasingOrder: @noescape (T, T) -> Bool) {
// self.elements = self.elements.sorted(by: areInIncreasingOrder)
// }

/**
Returns a `DataSet<T>` with the elements sorted by a provided block.

This is the default implementation `sort()` modified to return `DataSet<T>` rather than `Array<T>`.

- returns: A sorted `DataSet<T>` by the provided block.
*/
func sorted(by areInIncreasingOrder: @noescape (T, T) -> Bool) -> DataSet<T> {
return DataSet<T>(elements: self.elements.sorted(by: areInIncreasingOrder))
}

func sorted() -> DataSet<T> {
return self.sorted(by: { $0 < $1 })
}
}

Answer

Your DataSet is a BidirectionalCollection. The sort() you're trying to use requires a RandomAccessCollection. The most important thing you need to add is an Indicies typealias.

typealias Indices = Array<Element>.Indices

Here's my version of your type:

protocol Plottable: Comparable {}
extension Int: Plottable {}

struct DataSet<Element: Plottable>:  MutableCollection, RandomAccessCollection {
    private var elements: [Element] = []

    typealias Indices = Array<Element>.Indices

    init(elements data: [Element] = []) {
        self.elements = data
    }

    var startIndex: Int {
        return elements.startIndex
    }

    var endIndex: Int {
        return elements.endIndex
    }

    func index(after i: Int) -> Int {
        return elements.index(after: i)
    }

    func index(before i: Int) -> Int {
        return elements.index(before: i)
    }

    subscript(position: Int) -> Element {
        get {
            return elements[position]
        }
        set {
            elements[position] = newValue
        }
    }

    subscript(bounds: Range<Int>) -> DataSet<Element> {
        get {
            return DataSet(elements: Array(elements[bounds]))
        }
        set {
            elements[bounds] = ArraySlice(newValue.elements)
        }
    }
}

var data = DataSet(elements: [4,2,3,1])
data.sort()
print(data.elements) // [1,2,3,4]

You don't actually need an Iterator if you don't want one. Swift will give you Sequence automatically if you implement Collection.