Following the topic about SortedCollection I've decided to give some thought to the matter and possible implementation.
Like mentioned by @nnnnnnnn here, possible design for a SortedColelction
protocol should include the comparison closure that defines the order of elements. And I didn't really identify any other mandatory requirements so far:
public protocol SortedCollection: Collection {
var areInIncreasingOrder: (Element, Element) -> Bool { get }
}
Thinking of the mutable version of this protocol, i believe that the insert should return the insertion index for easy following accesses.
//Mutable version
public protocol MutableSortedCollection: SortedCollection {
@discardableResult
mutating func insert(_ element: Element) -> Index
@discardableResult
mutating func remove(_ element: Element) -> Element
}
Slices of a Sorted collection should be SortedColelctions themselves:
extension Slice: SortedCollection where Base: SortedCollection {
public var areInIncreasingOrder: (Base.Element, Base.Element) -> Bool {
return base.areInIncreasingOrder
}
}
finally we can have extensions to search for elements indexes taking advantage of the fact that the collection is actually sorted
extension SortedCollection {
func _firstIndex(of element: Element) -> Index {
var lo = startIndex
var hi = endIndex
while lo < hi {
let half = distance(from: lo, to: hi) / 2
let mid = index(lo, offsetBy: half)
let midEl = self[mid]
if areInIncreasingOrder(midEl, element) {
lo = index(after: mid)
} else {
hi = mid
}
}
return lo
}
func _lastIndex(of element: Element) -> Index {
var lo = startIndex
var hi = endIndex
while lo < hi {
let half = distance(from: lo, to: hi) / 2
let mid = index(lo, offsetBy: half)
let midEl = self[mid]
if areInIncreasingOrder(element, midEl) {
hi = mid
} else {
lo = index(after: mid)
}
}
return lo
}
}
public extension SortedCollection where Element: Equatable {
func firstIndex(of element: Element) -> Index? {
guard !isEmpty else { return nil }
let index = _firstIndex(of: element)
return self[index] == element ? index : nil
}
@inlinable func contains(_ element: Element) -> Bool {
return firstIndex(of: element) != nil
}
}
public extension SortedCollection where Self: BidirectionalCollection, Element: Equatable {
func lastIndex(of element: Element) -> Index? {
guard !isEmpty else { return nil }
var index = _lastIndex(of: element)
guard index > startIndex else { return nil }
formIndex(before: &index)
return self[index] == element ? index : nil
}
}
Having this setup we can write a SortedArray
struct
public struct SortedArray<Element>: RandomAccessCollection, SortedCollection {
public typealias Index = Int
private var storage: [Element]
public let areInIncreasingOrder: (Element, Element) -> Bool
public var startIndex: Int { return storage.startIndex }
public var endIndex: Int { return storage.endIndex }
public subscript(position: Int) -> Element {
get { return storage[position] }
}
public func index(after i: Int) -> Int {
return storage.index(after: i)
}
}
//Initializer
extension SortedArray {
public init<S: Sequence>(_ sequence: S, by areInIncreasingOrder: @escaping (Element, Element) -> Bool) where S.Element == Element {
self.areInIncreasingOrder = areInIncreasingOrder
self.storage = sequence.sorted(by: areInIncreasingOrder)
}
public init(by areInIncreasingOrder: @escaping (Element, Element) -> Bool) {
self.areInIncreasingOrder = areInIncreasingOrder
self.storage = [Element]()
}
}
extension SortedArray where Element: Comparable {
public init<S: Sequence>(_ sequence: S) where S.Element == Element {
self.areInIncreasingOrder = ( < )
self.storage = sequence.sorted(by: <)
}
public init() {
self.areInIncreasingOrder = ( < )
self.storage = [Element]()
}
}
extension SortedArray: CustomStringConvertible {
public var description: String { return storage.description }
}
Just brainstorming about this implementation. I would be interested in which are your thoughts and where we should go from here to get a proposal.