Skip to content

Instantly share code, notes, and snippets.

@CTMacUser
Last active March 2, 2019 06:11
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 CTMacUser/02ad9f009bcde87d2a1f9c749697bcc7 to your computer and use it in GitHub Desktop.
Save CTMacUser/02ad9f009bcde87d2a1f9c749697bcc7 to your computer and use it in GitHub Desktop.
Extensions to MutableCollection to swap multiple elements at a time, plus support methods to rotate, and example methods to move elements.
// RotateSwapMove.swift by Daryle Walker
extension MutableCollection {
/**
Rotates the elements such that the value at the given index is now at `startIndex`.
Passing `startIndex` as `i` has no effect.
The method applies a left-rotation, bringing the target element's value to `startIndex`.
var letters = ["A", "B", "C", "D", "E", "F"]
letters.rotate(toFirst: letters.index(after: letters.startIndex))
print(String(letters))
// Prints "BCDEFA"
- Precondition: `i` must be a valid index of this collection and not equal to `endIndex`.
- Parameter i: The index of the element whose value will move to `startIndex`.
- Returns: The index of the element where the value originally at `startIndex` went.
- Postcondition: The new value is a left-rotation of the old; `newValue == oldValue[i...] + oldValue[..<i]`.
- Complexity: O(*n*), where *n* is the collection's length.
*/
@discardableResult
mutating public func rotate(toFirst i: Index) -> Index {
precondition(i != endIndex)
guard i != startIndex else { return startIndex }
// Adapted from <http://www.cplusplus.com/reference/algorithm/rotate/>.
var pivot = i, next = pivot, start = startIndex
let startOffset = distance(from: pivot, to: endIndex)
while start != next {
swapAt(start, next)
formIndex(after: &start)
formIndex(after: &next)
if next == endIndex {
next = pivot
} else if start == pivot {
pivot = next
}
}
return index(startIndex, offsetBy: startOffset)
}
/**
Rotates the elements such that the value at the given index is now at the last valid index before `endIndex`.
Passing the index value before `endIndex` as `i` has no effect.
The method applies a right-rotation, bringing the target element's value towards `endIndex`.
var letters = ["A", "B", "C", "D", "E", "F"]
letters.rotate(toLast: letters.index(after: letters.startIndex))
print(String(letters))
// Prints "CDEFAB"
- Precondition: `i` must be a valid index of this collection and not equal to `endIndex`.
- Parameter i: The index of the element whose value will move to the last valid element.
- Returns: The index of where the value originally at `startIndex` went.
- Postcondition: The new value is a right-rotation of the old; `newValue == oldValue[i>..] + oldValue[...i]`.
- Complexity: O(*n*), where *n* is the collection's length.
*/
@discardableResult
mutating public func rotate(toLast i: Index) -> Index {
let shiftedPivot = index(i, offsetBy: +1, limitedBy: endIndex)!
guard shiftedPivot != endIndex else { return startIndex }
return rotate(toFirst: shiftedPivot)
}
}
extension MutableCollection {
/**
Exchanges the values within the specified same-length ranges of the
collection.
Both parameters must bound valid index ranges of the collection
with the same length and either none or all elements in common.
Using the same range for `r` and `s` has no effect.
- Precondition:
- `r` and `s` either are identical or have no overlap.
- `self[r].count == self[s].count`.
- Parameter r: The range for the first subsequence to swap.
- Parameter s: The range for the second susequence to swap.
- Postcondition: No effect if the ranges are identical. Otherwise,
the targeted subsequences exchange their relative positions
against all the maximum-length untargeted subsequences.
- Complexity: O(*n*), where *n* is the length of the targeted
subsequences.
*/
public mutating func swapSubranges<R: RangeExpression, S: RangeExpression>(_ r: R, ofEqualLength s: S) where R.Bound == Index, S.Bound == Index {
let rr = r.relative(to: self), ss = s.relative(to: self)
precondition(rr == ss || !rr.overlaps(ss))
var ri = rr.lowerBound, si = ss.lowerBound
while ri < rr.upperBound, si < ss.upperBound {
swapAt(ri, si)
formIndex(after: &ri)
formIndex(after: &si)
}
precondition(ri == rr.upperBound && si == ss.upperBound, "The subsequences don't have the same count")
}
/**
Given a pivot splitting the collection, exchanges the partitions'
order.
The parameter must be a valid index of the collection. Indicating
an empty partition by passing either `startIndex` or `endIndex`
returns the other endpoint and has no other effect.
- Parameter secondStart: The start index for the second subsequence
(and past-the-end index for the first subsequence).
- Postcondition:
- `newSelf[..<returnValue] == oldSelf[secondStart...]`.
- `newSelf[returnValue...] == oldSelf[..<secondStart]`.
- Returns: The start index for the relocated first subsequence (and
past-the-end index for the relocated second subsequence).
- Complexity: O(*n*), where *n* is the length of the collection.
*/
public mutating func swapPartitions(along secondStart: Index) -> Index {
guard secondStart < endIndex else { return startIndex }
guard secondStart > startIndex else { return endIndex }
return rotate(toFirst: secondStart)
}
/**
Given a range sectioning the collection, exchange the prefix and
suffix element values.
The parameter must bound a valid range of indices of the collection.
Any of the prefix, parameter, or suffix subsequences may be empty.
- Parameter r: The range for the subsequence that will stay in
the middle.
- Postcondition: `newSelf == oldSelf[r.upperBound...] + oldSelf[r] + oldSelf[..<r.lowerBound]`.
- Returns: The new position of the middle subsequence. It will
equal the argument only when the prefix and suffix subsequences
have the same length.
- Complexity: O(*n*), where *n* is the length of the collection.
*/
public mutating func swapAdfixes(around r: Range<Index>) -> Range<Index> {
switch (r.lowerBound == startIndex, r.upperBound == endIndex) {
case (true, true):
// No prefix nor suffix; no change.
return r
case (true, false):
// Adfix moves from suffix to prefix.
let result = swapPartitions(along: r.upperBound)
return result..<endIndex
case (false, true):
// Adfix moves from prefix to suffix.
let result = swapPartitions(along: r.lowerBound)
return startIndex..<result
case (false, false):
// Build this in terms of simpler swaps.
// (P|R|S -> P|S|R -> S|P|R -> S|R|P)
var firstDivider = r.lowerBound, secondDivider = r.upperBound
secondDivider = self[firstDivider...].swapPartitions(along: secondDivider) // R ↔︎ S
firstDivider = self[..<secondDivider].swapPartitions(along: firstDivider) // S ↔︎ P
secondDivider = self[firstDivider...].swapPartitions(along: secondDivider) // P ↔︎ R
return firstDivider..<secondDivider
}
}
/**
Exchanges the values of elements within the specified ranges of
the collection, and returns the updated bounds.
Both parameters must bound valid index ranges of the collection.
The subsequences for the ranges must have either no elements in
common or all of (at least) one subsequence's elements in common.
There is no effect in the latter case. Non-identical ranges do
not have to be the same length.
- Precondition: The intersection of `r` and `s` must be exactly
∅, `r`, or `s`.
- Parameter r: The range for the first subsequence to swap.
- Parameter s: The range for the second susequence to swap.
- Postcondition: No effect if the ranges are identical or one of
them is completely within the other. Otherwise, the targeted
subsequences exchange their relative positions against all the
maximum-length untargeted subsequences, and `r` and `s` are
updated to their subsequences' new ranges.
- Returns: The new range for the subsequence between `r` and `s`,
or `nil` if no effect occured.
- Complexity: O(*n*), where *n* is the length of the collection.
*/
@discardableResult
public mutating func swapSubranges(_ r: inout Range<Index>, _ s: inout Range<Index>) -> Range<Index>? {
guard r.lowerBound <= s.upperBound else { return swapRanges(&s, &r) }
guard r.upperBound <= s.lowerBound else {
// The first guard above misses when reversed-order ranges abut.
guard r.lowerBound != s.upperBound else { return swapRanges(&s, &r) }
// A consistent result is still possible if one range is totally within another.
precondition(r.lowerBound <= s.lowerBound && r.upperBound >= s.upperBound || s.lowerBound <= r.lowerBound && s.upperBound >= r.upperBound)
return nil
}
let outBounds = r.lowerBound..<s.upperBound, inBounds = r.upperBound..<s.lowerBound
let newMiddle = self[outBounds].swapAdfixes(around: inBounds)
s = outBounds.lowerBound..<newMiddle.lowerBound
r = newMiddle.upperBound..<outBounds.upperBound
return newMiddle
}
/**
Exchanges the values of elements within the specified ranges of
the collection.
Both parameters must bound valid index ranges of the collection.
The subsequences for the ranges must have either no elements in
common or all of (at least) one subsequence's elements in common.
There is no effect in the latter case. Non-identical ranges do
not have to be the same length.
- Precondition: The intersection of `r` and `s` must be exactly
∅, `r`, or `s`.
- Parameter r: The range for the first subsequence to swap.
- Parameter s: The range for the second susequence to swap.
- Postcondition: No effect if the ranges are identical or one of
them is completely within the other. Otherwise, the targeted
subsequences exchange their relative positions against all the
maximum-length untargeted subsequences.
- Complexity: O(*n*), where *n* is the length of the collection.
*/
public mutating func swapSubranges<R: RangeExpression, S: RangeExpression>(_ r: R, _ s: S) where R.Bound == Index, S.Bound == Index {
var dummyR = r.relative(to: self), dummyS = s.relative(to: self)
_ = swapSubranges(&dummyR, &dummyS)
}
}
extension MutableCollection {
/**
Moves the specfied subrange of elements to the specified position.
- Precondition: `i` cannot be within `r`.
- Parameter r: The subrange of the collection to move. The bounds
of the range must be valid indices of the collection.
- Parameter i: The position at which to relocate the element. `i`
must be a valid index into the collection.
- Postcondition: No effect if `i` is at either the start or end of `r`.
Otherwise, the targeted subsequence is moved to start at `i`, with the
elements values at and after `i` moved towards `endIndex`, countered by
the element values after `r` moving towards `startIndex`, leaving the
element values not in `i`, `r`, or between `i` and `r` in place. If `i`
is `endIndex`, the targeted subsequence is relocated to append the
untargeted elements.
- Complexity: O(*n*), where *n* is the length of the collection.
*/
public mutating func moveSubrange<R: RangeExpression>(_ r: R, to i: Index) where R.Bound == Index {
let rr = r.relative(to: self)
switch i {
case ...rr.lowerBound:
_ = self[i..<rr.upperBound].swapPartitions(along: rr.lowerBound)
case rr.upperBound...:
_ = self[rr.lowerBound..<i].swapPartitions(along: rr.upperBound)
default:
preconditionFailure("Can't relocate elements to within themselves.")
}
}
/**
Moves an element from one specified position to another.
- Precondition: `i` and `j` must be valid indices into the collection,
where `i` further cannot be `endIndex`.
- Parameter i: The position of the element to be relocated.
- Parameter j: The position where the target will be moved to.
- Postcondition: No effect if `i == j`. Otherwise, the element at `i`
is relocated to `j` and the element values at each side of the target
are closed up. If `j` is `endIndex`, the target value is moved to be
the last element.
- Complexity: O(*n*), where *n* is the length of the collection.
*/
public mutating func move(from i: Index, to j: Index) {
moveSubrange(i..<index(after: i), to: j)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment