Created
November 12, 2016 20:25
-
-
Save TheNamesJamesW/beb0a5016ee88bbfdcf562e5e0e412a0 to your computer and use it in GitHub Desktop.
A filter that can be applied on a Sequence but stored (and passed around) independently.
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
/** | |
Defines a filter that can be applied on a `Sequence` instance but stored (and passed around) independently. | |
Useful if switching filters frequently, creating dynamic subsets of a given dataset and passing between abstraction levels (i.e. View level could create this and pass to the Model) | |
Example: | |
``` | |
let strings = ["c", "b", "b", "b", "a"] | |
var predicate: Filter<String, [String]> | |
predicate = .compound(.sort, .first(3)) | |
predicate.apply(to: strings) | |
// ["a", "b", "b"] | |
``` | |
The following two `Filter`s are equivalent | |
``` | |
predicate = .compound(.equals("b"), .first(2)) | |
predicate.apply(to: strings) | |
// ["b", "b"] | |
predicate = Filter.equals("b").compounding(.first(2)) | |
predicate.apply(to: strings) | |
// ["b", "b"] | |
``` | |
However, this one is not, as `.first(2)` is applied **before** `.equals("b")` | |
``` | |
predicate = Filter.first(2).compounding(.equals("b")) | |
predicate.apply(to: strings) | |
// ["b"] | |
``` | |
*/ | |
enum Filter<Element, C: SubSequenceConvertible & ArrayConvertibleSequence> where C.Iterator.Element == Element, C.SubSequence: Sequence, C.SubSequence.Iterator.Element == Element { | |
case first(Int) | |
case last(Int) | |
case filterWhere((Element) -> Bool) | |
case sortWhere((Element, Element) -> Bool) | |
indirect case compound(Filter, Filter) | |
} | |
extension Filter { | |
func apply(to input: C) -> C { | |
switch self { | |
case .first(let limit): | |
return C(input.prefix(limit)) | |
case .last(let limit): | |
return C(input.suffix(limit)) | |
case .filterWhere(let predicate): | |
return C(input.filter(predicate)) | |
case .sortWhere(let predicate): | |
return C(input.sorted(by: predicate)) | |
case .compound(let a, let b): | |
return b.apply(to: a.apply(to: input)) | |
} | |
} | |
} | |
extension Filter { | |
func compounding(_ other: Filter) -> Filter { | |
return .compound(self, other) | |
} | |
} | |
extension Filter where Element: Equatable { | |
static func equals(_ other: Element) -> Filter { | |
return Filter.filterWhere({ | |
$0 == other | |
}) | |
} | |
} | |
extension Filter where Element: Comparable { | |
static var sort: Filter { | |
return Filter.sortWhere({ | |
$0 < $1 | |
}) | |
} | |
} | |
extension Filter { | |
/** | |
Determines generic types automatically. Useful if you don't know the exact type of `Sequence` being used or what its `Element` is | |
``` | |
let dictionary = ["a" : 1, | |
"b" : 2, | |
"c" : 3] | |
var x = Filter.create(.first(1), for: dictionary) | |
// `x` is now of Type `Filter<(String, Int), Dictionary<String, Int>>` | |
// ... | |
x = .first(2) | |
// ... | |
x.apply(to: dictionary) | |
``` | |
- Parameter filter: The filter you want to apply | |
- Parameter for: The sequence whose Type you want the filter to apply to | |
*/ | |
static func create(_ filter: Filter, for _: C) -> Filter { | |
return filter | |
} | |
} | |
// MARK: - Required protocols and conformance | |
/** | |
Defines a `Sequence` that can be created from a `SubSequence` of itself | |
``` | |
let array = [1,2,3,4] | |
// Array<Int>.Type | |
let toSubArray = array[0..<4] | |
// ArraySlice<Int>.Type | |
let andBack = Array(toSubArray) | |
// Array<Int>.Type | |
``` | |
*/ | |
protocol SubSequenceConvertible: Sequence { | |
init(_ subSequence: SubSequence) | |
} | |
/** | |
Defines a `Sequence` that can be created from an `Array` of its `Element`s | |
``` | |
let dictionary = ["a" : 1, | |
"b" : 2, | |
"c" : 3] | |
// Dictionary<String, Int>.Type | |
let filtered = dictionary.filter { | |
$0.value <= 2 | |
} | |
// Array<(String, Int)>.Type | |
let dictAgain = Dictionary(filtered) | |
// Dictionary<String, Int>.Type | |
``` | |
*/ | |
protocol ArrayConvertibleSequence: Sequence { | |
init(_ array: Array<Iterator.Element>) | |
} | |
extension Array: SubSequenceConvertible, ArrayConvertibleSequence {} | |
extension Dictionary: SubSequenceConvertible, ArrayConvertibleSequence { | |
internal init(_ subSequence: Slice<Dictionary<Key, Value>>) { | |
self.init(subSequence) | |
} | |
init(_ elements: [Element]) { | |
self.init() | |
for (k, v) in elements { | |
self[k] = v | |
} | |
} | |
} | |
//MARK: - Filter examples | |
let strings = ["c", "b", "b", "b", "a"] | |
var predicate: Filter<String, [String]> | |
predicate = .compound(.sort, .first(3)) | |
predicate.apply(to: strings) | |
// ["a", "b", "b"] | |
predicate = .compound(.equals("b"), .first(2)) | |
predicate.apply(to: strings) | |
// ["b", "b"] | |
predicate = Filter.equals("b").compounding(.first(2)) | |
predicate.apply(to: strings) | |
// ["b", "b"] | |
predicate = Filter.first(2).compounding(.equals("b")) | |
predicate.apply(to: strings) | |
// ["b"] | |
//MARK: - ArrayConvertibleSequence example | |
let array = [1,2,3,4] | |
// Array<Int>.Type | |
let toSubArray = array[0..<4] | |
// ArraySlice<Int>.Type | |
let andBack = Array(toSubArray) | |
// Array<Int>.Type | |
//MARK: - SubSequenceConvertible example | |
let dictionary = ["a" : 1, | |
"b" : 2, | |
"c" : 3] | |
// Dictionary<String, Int>.Type | |
let filtered = dictionary.filter { | |
$0.value <= 2 | |
} | |
// Array<(String, Int)>.Type | |
let dictAgain = Dictionary(filtered) | |
// Dictionary<String, Int>.Type |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment