Last active
March 2, 2019 06:11
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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