/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import SwiftyJSON // Pipelining. precedencegroup PipelinePrecedence { associativity: left } infix operator |> : PipelinePrecedence public func |> (x: T, f: (T) -> U) -> U { return f(x) } // Basic currying. public func curry(_ f: @escaping (A) -> B) -> (A) -> B { return { a in return f(a) } } public func curry(_ f: @escaping (A, B) -> C) -> (A) -> (B) -> C { return { a in return { b in return f(a, b) } } } public func curry(_ f: @escaping (A, B, C) -> D) -> (A) -> (B) -> (C) -> D { return { a in return { b in return { c in return f(a, b, c) } } } } public func curry(_ f: @escaping (A, B, C, D) -> E) -> (A, B, C) -> (D) -> E { return { (a, b, c) in return { d in return f(a, b, c, d) } } } // Function composition. infix operator • public func •(f: @escaping (T) -> U, g: @escaping (U) -> V) -> (T) -> V { return { t in return g(f(t)) } } public func •(f: @escaping (T) -> Void, g: @escaping () -> V) -> (T) -> V { return { t in f(t) return g() } } public func •(f: @escaping () -> Void, g: @escaping () -> V) -> () -> V { return { f() return g() } } // Why not simply provide an override for ==? Well, that's scary, and can accidentally recurse. // This is enough to catch arrays, which Swift will delegate to element-==. public func optArrayEqual(_ lhs: [T]?, rhs: [T]?) -> Bool { switch (lhs, rhs) { case (.none, .none): return true case (.none, _): return false case (_, .none): return false default: // This delegates to Swift's own array '==', which calls T's == on each element. return lhs! == rhs! } } /** * Given an array, return an array of slices of size `by` (possibly excepting the last slice). * * If `by` is longer than the input, returns a single chunk. * If `by` is less than 1, acts as if `by` is 1. * If the length of the array isn't a multiple of `by`, the final slice will * be smaller than `by`, but never empty. * * If the input array is empty, returns an empty array. */ public func chunk(_ arr: [T], by: Int) -> [ArraySlice] { var result = [ArraySlice]() var chunk = -1 let size = max(1, by) for (index, elem) in arr.enumerated() { if index % size == 0 { result.append(ArraySlice()) chunk += 1 } result[chunk].append(elem) } return result } public func chunkCollection(_ items: T, by: Int, f: ([E]) -> [X]) -> [X] where T.Iterator.Element == E { assert(by >= 0) let max = by > 0 ? by : 1 var i = 0 var acc: [E] = [] var results: [X] = [] var iter = items.makeIterator() while let item = iter.next() { if i >= max { results.append(contentsOf: f(acc)) acc = [] i = 0 } acc.append(item) i += 1 } if !acc.isEmpty { results.append(contentsOf: f(acc)) } return results } public extension Sequence { // [T] -> (T -> K) -> [K: [T]] // As opposed to `groupWith` (to follow Haskell's naming), which would be // [T] -> (T -> K) -> [[T]] func groupBy(_ selector: (Self.Iterator.Element) -> Key, transformer: (Self.Iterator.Element) -> Value) -> [Key: [Value]] { var acc: [Key: [Value]] = [:] for x in self { let k = selector(x) var a = acc[k] ?? [] a.append(transformer(x)) acc[k] = a } return acc } func zip(_ elems: S) -> [(Self.Iterator.Element, S.Iterator.Element)] { var rights = elems.makeIterator() return self.flatMap { lhs in guard let rhs = rights.next() else { return nil } return (lhs, rhs) } } } public func optDictionaryEqual(_ lhs: [K: V]?, rhs: [K: V]?) -> Bool { switch (lhs, rhs) { case (.none, .none): return true case (.none, _): return false case (_, .none): return false default: return lhs! == rhs! } } /** * Return members of `a` that aren't nil, changing the type of the sequence accordingly. */ public func optFilter(_ a: [T?]) -> [T] { return a.flatMap { $0 } } /** * Return a new map with only key-value pairs that have a non-nil value. */ public func optFilter(_ source: [K: V?]) -> [K: V] { var m = [K: V]() for (k, v) in source { if let v = v { m[k] = v } } return m } /** * Map a function over the values of a map. */ public func mapValues(_ source: [K: T], f: ((T) -> U)) -> [K: U] { var m = [K: U]() for (k, v) in source { m[k] = f(v) } return m } public func findOneValue(_ map: [K: V], f: (V) -> Bool) -> V? { for v in map.values { if f(v) { return v } } return nil } /** * Take a JSON array, returning the String elements as an array. * It's usually convenient for this to accept an optional. */ public func jsonsToStrings(_ arr: [JSON]?) -> [String]? { return arr?.flatMap { $0.stringValue } } // Encapsulate a callback in a way that we can use it with NSTimer. private class Callback { private let handler:() -> Void init(handler:@escaping () -> Void) { self.handler = handler } @objc func go() { handler() } } /** * Taken from http://stackoverflow.com/questions/27116684/how-can-i-debounce-a-method-call * Allows creating a block that will fire after a delay. Resets the timer if called again before the delay expires. **/ public func debounce(_ delay: TimeInterval, action:@escaping () -> Void) -> () -> Void { let callback = Callback(handler: action) var timer: Timer? return { // If calling again, invalidate the last timer. if let timer = timer { timer.invalidate() } timer = Timer(timeInterval: delay, target: callback, selector: #selector(Callback.go), userInfo: nil, repeats: false) RunLoop.current.add(timer!, forMode: RunLoopMode.defaultRunLoopMode) } }