Skip to content

Instantly share code, notes, and snippets.

@natecook1000
Last active February 25, 2019 05:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save natecook1000/5b22046d78edc274df53e2a7fc189f27 to your computer and use it in GitHub Desktop.
Save natecook1000/5b22046d78edc274df53e2a7fc189f27 to your computer and use it in GitHub Desktop.
struct EachPairCollection<Base: Collection>: Collection {
var base: Base
struct Index: Comparable {
var first: Base.Index
var second: Base.Index
// This is a little weird. Equality and comparisons
// are only based on `second`, to allow an endIndex
// to be created where both `first` and `second`
// are the endIndex of the base collection.
// This sentinel endIndex needs to compare equal
// to the "natural" endIndex, which has the
// penultimate index at `first`.
static func ==(lhs: Index, rhs: Index) -> Bool {
return lhs.second == rhs.second
}
static func < (lhs: Index, rhs: Index) -> Bool {
return lhs.second < rhs.second
}
}
var startIndex: Index {
if base.isEmpty { return endIndex }
return Index(first: base.startIndex, second: base.index(after: base.startIndex))
}
var endIndex: Index {
return Index(first: base.endIndex, second: base.endIndex)
}
func index(after i: Index) -> Index {
assert(i.second != base.endIndex, "Can't advance past endIndex")
return Index(first: i.second, second: base.index(after: i.second))
}
subscript(i: Index) -> (Base.Element, Base.Element) {
return (base[i.first], base[i.second])
}
}
extension EachPairCollection: BidirectionalCollection where Base: BidirectionalCollection {
func index(before i: Index) -> Index {
assert(i.first != base.startIndex, "Can't move before startIndex")
let previousSecond = i == endIndex
? base.index(before: base.endIndex)
: i.first
return Index(first: base.index(before: previousSecond), second: previousSecond)
}
}
extension Collection {
func eachPair() -> EachPairCollection<Self> {
return EachPairCollection(base: self)
}
}
struct SlicingCollection<Base: Collection>: Collection {
var base: Base
var predicate: (Base.Element, Base.Element) -> Bool
struct Index: Comparable {
var start: Base.Index
var end: Base.Index
// Ignoring `end` in these comparisons, as it's
// essentially a cache value, and is a bit wonky
// with the endIndex anyway.
static func ==(lhs: Index, rhs: Index) -> Bool {
return lhs.start == rhs.start
}
static func < (lhs: Index, rhs: Index) -> Bool {
return lhs.start < rhs.start
}
}
var startIndex: Index {
return Index(
start: base.startIndex,
end: _findNextDivision(after: base.startIndex))
}
var endIndex: Index {
return Index(start: base.endIndex, end: base.endIndex)
}
func _findNextDivision(after baseIndex: Base.Index) -> Base.Index {
return base[baseIndex...].indices
.eachPair()
.first(where: { indexPair in
predicate(base[indexPair.0], base[indexPair.1])
})?.1
?? base.endIndex
}
func index(after i: Index) -> Index {
return Index(
start: i.end,
end: _findNextDivision(after: i.end))
}
subscript(i: Index) -> Base.SubSequence {
guard i.start < base.endIndex
else { fatalError("Can't subscript past the end") }
return base[i.start..<index(after: i).start]
}
}
extension SlicingCollection: BidirectionalCollection
where Base: BidirectionalCollection
{
func index(before i: Index) -> Index {
let previousStart = base[..<i.start].indices
.eachPair()
.last(where: { indexPair in
predicate(base[indexPair.0], base[indexPair.1])
})?.1
?? base.startIndex
return Index(start: previousStart, end: i.start)
}
}
extension Collection {
func sliceBetween(where predicate: @escaping (Element, Element) -> Bool) -> SlicingCollection<Self> {
return SlicingCollection(base: self, predicate: predicate)
}
func sliceBefore(_ predicate: @escaping (Element) -> Bool) -> SlicingCollection<Self> {
return SlicingCollection(base: self) { _, second in
predicate(second)
}
}
func sliceAfter(_ predicate: @escaping (Element) -> Bool) -> SlicingCollection<Self> {
return SlicingCollection(base: self) { first, _ in
predicate(first)
}
}
}
let numbers = [1, 1, 2, 2, 2, 3, 3, 4, 4, 4, 4, 4]
let slicer = numbers.sliceBetween(where: !=)
slicer.first // [1, 1]
let splitsies = Array(slicer) // [[1, 1], [2, 2, 2], [3, 3], [4, 4, 4, 4, 4]]
slicer.last // [4, 4, 4, 4]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment