Skip to content

Instantly share code, notes, and snippets.

@Qata
Last active November 25, 2019 01:46
Show Gist options
  • Save Qata/072f12a13f5d658edc5ec211819913a0 to your computer and use it in GitHub Desktop.
Save Qata/072f12a13f5d658edc5ec211819913a0 to your computer and use it in GitHub Desktop.
public extension Collection {
/// Allows lensing properties before comparison by the std lib `firstIndex(where:)` function, using the `where` closure.
func firstIndex<T>(lensing path: KeyPath<Element, T>, where predicate: (T) -> Bool) -> Index? {
return firstIndex {
predicate($0[keyPath: path])
}
}
}
public extension Collection {
/// Same functionality as `joined` but without the flattening.
func intersperse(element: Element) -> [Element] {
Array(
flatMap { [$0, element] }.dropLast()
)
}
func chunked<I: BinaryInteger>(stride length: I) -> [SubSequence] {
stride(from: 0, to: count, by: numericCast(length))
.map { dropFirst($0).prefix(numericCast(length)) }
}
/// Create `strides.count` chunks of the size given by their associated `BinaryInteger`.
/// If a chunk's length cannot be satisfied then it'll return the maximum length available, but discards when the length is `0`.
func chunked<I: BinaryInteger>(strides: I...) -> [SubSequence] {
chunked(strides: strides)
}
/// Create `strides.count` chunks of the size given by their associated `BinaryInteger`.
/// If a chunk's length cannot be satisfied then it'll return the maximum length available, but discards when the length is `0`.
func chunked<S: Sequence>(strides: S) -> [SubSequence] where S.Element: BinaryInteger {
zip(strides, strides.scan(0, +).prefix { $0 < count })
.map { dropFirst(numericCast($1)).prefix(numericCast($0)) }
}
func rotated<I: BinaryInteger>(by steps: I) -> [Element] {
let normalised = Int(steps) % count
switch normalised.signum() {
case 0:
return .init(self)
case 1:
return .init([suffix(normalised), dropLast(normalised)].joined())
case -1:
return .init([dropFirst(-normalised), prefix(-normalised)].joined())
default:
fatalError()
}
}
}
public extension Collection where Element: BinaryFloatingPoint {
/// Normalizes any floating point numbers to fit within the new range.
/// The range of the values provided is assumed to extend from the min element to the max element.
func normalize(into range: ClosedRange<Element>) -> [Element] {
guard let min = self.min(), let max = self.max() else { return [] }
return normalize(from: min...max, into: range)
}
/// Normalizes any floating point numbers to fit within the new range.
func normalize(from: ClosedRange<Element>, into: ClosedRange<Element>) -> [Element] {
let fromMaxMinusMin = from.upperBound - from.lowerBound
let intoMaxMinusMin = into.upperBound - into.lowerBound
return map {
Swift.max(
into.lowerBound,
Swift.min(
into.upperBound,
($0 - from.lowerBound) / fromMaxMinusMin * intoMaxMinusMin + into.lowerBound
)
)
}
}
}
import SwiftCheck
import XCTest
extension ClosedRange: Arbitrary where Bound: Arbitrary {
public static var arbitrary: Gen<ClosedRange<Bound>> {
return Gen
.zip(Bound.arbitrary, Bound.arbitrary)
.map { Swift.min($0, $1)...Swift.max($0, $1) }
}
}
class CollectionTests: XCTestCase {
func testChunked() {
property("All chunked arrays other than the last one will have n elements") <- forAll { (i: Positive<Int>, n: UInt) in
Array(repeating: (), count: Int(n))
.chunked(stride: i.getPositive)
.dropLast()
.allSatisfy { $0.count == i.getPositive }
}
property("If the length is not divisible by the stride, the last array will have n % i elements") <- forAll { (i: Positive<Int>, n: Positive<Int>) in
n.getPositive % i.getPositive != 0 ==> {
Array(repeating: (), count: n.getPositive)
.chunked(stride: i.getPositive)
.last?
.count == (n.getPositive % i.getPositive)
}
}
}
func testNormalize() {
property("Normalize will produce values within the range") <- forAll { (values: [Double], range: ClosedRange<Double>) in
values.normalize(into: range).allSatisfy(range.contains)
}
property("Normalize will produce values within the to range when manually supplied a from") <- forAllNoShrink(Double.arbitrary.proliferateNonEmpty, ClosedRange<Double>.arbitrary, ClosedRange<Double>.arbitrary) { values, fromRange, toRange in
(fromRange.lowerBound <= values.min()! && fromRange.upperBound >= values.max()!) ==> {
values.normalize(from: fromRange, into: toRange).allSatisfy(toRange.contains)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment