Skip to content
Permalink
master
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
// RangeReplaceableCollectionExtensions.swift - Copyright 2020 SwifterSwift
// MARK: - Initializers
public extension RangeReplaceableCollection {
/// SwifterSwift: Creates a new collection of a given size where for each position of the collection the value will be the result of a call of the given expression.
///
/// let values = Array(expression: "Value", count: 3)
/// print(values)
/// // Prints "["Value", "Value", "Value"]"
///
/// - Parameters:
/// - expression: The expression to execute for each position of the collection.
/// - count: The count of the collection.
init(expression: @autoclosure () throws -> Element, count: Int) rethrows {
self.init()
// swiftlint:disable:next empty_count
if count > 0 {
reserveCapacity(count)
while self.count < count {
append(try expression())
}
}
}
}
// MARK: - Methods
public extension RangeReplaceableCollection {
///  SwifterSwift: Returns a new rotated collection by the given places.
///
/// [1, 2, 3, 4].rotated(by: 1) -> [4,1,2,3]
/// [1, 2, 3, 4].rotated(by: 3) -> [2,3,4,1]
/// [1, 2, 3, 4].rotated(by: -1) -> [2,3,4,1]
///
/// - Parameter places: Number of places that the array be rotated. If the value is positive the end becomes the start, if it negative it's that start become the end.
/// - Returns: The new rotated collection.
func rotated(by places: Int) -> Self {
// Inspired by: https://ruby-doc.org/core-2.2.0/Array.html#method-i-rotate
var copy = self
return copy.rotate(by: places)
}
///  SwifterSwift: Rotate the collection by the given places.
///
/// [1, 2, 3, 4].rotate(by: 1) -> [4,1,2,3]
/// [1, 2, 3, 4].rotate(by: 3) -> [2,3,4,1]
/// [1, 2, 3, 4].rotated(by: -1) -> [2,3,4,1]
///
/// - Parameter places: The number of places that the array should be rotated. If the value is positive the end becomes the start, if it negative it's that start become the end.
/// - Returns: self after rotating.
@discardableResult
mutating func rotate(by places: Int) -> Self {
guard places != 0 else { return self }
let placesToMove = places % count
if placesToMove > 0 {
let range = index(endIndex, offsetBy: -placesToMove)...
let slice = self[range]
removeSubrange(range)
insert(contentsOf: slice, at: startIndex)
} else {
let range = startIndex..<index(startIndex, offsetBy: -placesToMove)
let slice = self[range]
removeSubrange(range)
append(contentsOf: slice)
}
return self
}
/// SwifterSwift: Removes the first element of the collection which satisfies the given predicate.
///
/// [1, 2, 2, 3, 4, 2, 5].removeFirst { $0 % 2 == 0 } -> [1, 2, 3, 4, 2, 5]
/// ["h", "e", "l", "l", "o"].removeFirst { $0 == "e" } -> ["h", "l", "l", "o"]
///
/// - Parameter predicate: A closure that takes an element as its argument and returns a Boolean value that indicates whether the passed element represents a match.
/// - Returns: The first element for which predicate returns true, after removing it. If no elements in the collection satisfy the given predicate, returns `nil`.
@discardableResult
mutating func removeFirst(where predicate: (Element) throws -> Bool) rethrows -> Element? {
guard let index = try firstIndex(where: predicate) else { return nil }
return remove(at: index)
}
/// SwifterSwift: Remove a random value from the collection.
@discardableResult
mutating func removeRandomElement() -> Element? {
guard let randomIndex = indices.randomElement() else { return nil }
return remove(at: randomIndex)
}
/// SwifterSwift: Keep elements of Array while condition is true.
///
/// [0, 2, 4, 7].keep(while: { $0 % 2 == 0 }) -> [0, 2, 4]
///
/// - Parameter condition: condition to evaluate each element against.
/// - Returns: self after applying provided condition.
/// - Throws: provided condition exception.
@discardableResult
mutating func keep(while condition: (Element) throws -> Bool) rethrows -> Self {
if let idx = try firstIndex(where: { try !condition($0) }) {
removeSubrange(idx...)
}
return self
}
/// SwifterSwift: Take element of Array while condition is true.
///
/// [0, 2, 4, 7, 6, 8].take( where: {$0 % 2 == 0}) -> [0, 2, 4]
///
/// - Parameter condition: condition to evaluate each element against.
/// - Returns: All elements up until condition evaluates to false.
func take(while condition: (Element) throws -> Bool) rethrows -> Self {
return Self(try prefix(while: condition))
}
/// SwifterSwift: Skip elements of Array while condition is true.
///
/// [0, 2, 4, 7, 6, 8].skip( where: {$0 % 2 == 0}) -> [6, 8]
///
/// - Parameter condition: condition to evaluate each element against.
/// - Returns: All elements after the condition evaluates to false.
func skip(while condition: (Element) throws -> Bool) rethrows -> Self {
guard let idx = try firstIndex(where: { try !condition($0) }) else { return Self() }
return Self(self[idx...])
}
/// SwifterSwift: Remove all duplicate elements using KeyPath to compare.
///
/// - Parameter path: Key path to compare, the value must be Equatable.
mutating func removeDuplicates<E: Equatable>(keyPath path: KeyPath<Element, E>) {
var items = [Element]()
removeAll { element -> Bool in
guard items.contains(where: { $0[keyPath: path] == element[keyPath: path] }) else {
items.append(element)
return false
}
return true
}
}
/// SwifterSwift: Remove all duplicate elements using KeyPath to compare.
///
/// - Parameter path: Key path to compare, the value must be Hashable.
mutating func removeDuplicates<E: Hashable>(keyPath path: KeyPath<Element, E>) {
var set = Set<E>()
removeAll { !set.insert($0[keyPath: path]).inserted }
}
/// SwifterSwift: Accesses the element at the specified position.
///
/// - Parameter offset: The offset position of the element to access. `offset` must be a valid index offset of the collection that is not equal to the `endIndex` property.
subscript(offset: Int) -> Element {
get {
return self[index(startIndex, offsetBy: offset)]
}
set {
let offsetIndex = index(startIndex, offsetBy: offset)
replaceSubrange(offsetIndex..<index(after: offsetIndex), with: [newValue])
}
}
/// SwifterSwift: Accesses a contiguous subrange of the collection’s elements.
///
/// - Parameter range: A range of the collection’s indices offsets. The bounds of the range must be valid indices of the collection.
subscript<R>(range: R) -> SubSequence where R: RangeExpression, R.Bound == Int {
get {
let indexRange = range.relative(to: 0..<count)
return self[index(startIndex, offsetBy: indexRange.lowerBound)..<index(startIndex,
offsetBy: indexRange.upperBound)]
}
set {
let indexRange = range.relative(to: 0..<count)
replaceSubrange(
index(startIndex, offsetBy: indexRange.lowerBound)..<index(startIndex, offsetBy: indexRange.upperBound),
with: newValue)
}
}
/**
SwifterSwift: Adds a new element at the end of the array, mutates the array in place
- Parameter newElement: The optional element to append to the array
*/
mutating func appendIfNonNil(_ newElement: Element?) {
guard let newElement = newElement else { return }
self.append(newElement)
}
/**
SwifterSwift: Adds the elements of a sequence to the end of the array, mutates the array in place
- Parameter newElements: The optional sequence to append to the array
*/
mutating func appendIfNonNil<S>(contentsOf newElements: S?) where Element == S.Element, S : Sequence {
guard let newElements = newElements else { return }
self.append(contentsOf: newElements)
}
}