Swift Generics, Constraints, and KeyPaths - swift

I'm aware of the limitations of generics in Swift and why they exist so this is not a question about compiler errors. Rather, I occasionally run into situations that seem as though they should be possible with some combination of the resources available in (i.e. generics, associatedTypes/protocols, etc) but can't seem to work out a solution.
In this example, I'm trying to come up with a Swift replacement for NSSortDescriptor (just for fun). It works perfect when you only have one descriptor but, as is often done with the NS version, it would be nice to create an array of SortDescriptors to sort on multiple keys.
The other trial here is using Swift KeyPaths. Because those require a Value type and the comparison requires a Comparable value, I'm running into trouble figuring out where/how to define the types to satisfy everything.
Is this possible? Here is one of the closest solutions I've come up with, but, as you can see at the bottom, it falls short when building an array.
Again, I understand why this doesn't work as is, but am curious if there is a way to achieve the desired functionality.
struct Person {
let name : String
let age : Int
}
struct SortDescriptor<T, V:Comparable> {
let keyPath: KeyPath<T,V>
let ascending : Bool
init(_ keyPath: KeyPath<T,V>, ascending:Bool = true) {
self.keyPath = keyPath
self.ascending = ascending
}
func compare(obj:T, other:T) -> Bool {
let v1 = obj[keyPath: keyPath]
let v2 = other[keyPath: keyPath]
return ascending ? v1 < v2 : v2 < v1
}
}
let jim = Person(name: "Jim", age: 30)
let bob = Person(name: "Bob", age: 35)
let older = SortDescriptor(\Person.age).compare(obj: jim, other: bob) // true
// Heterogeneous collection literal could only be inferred to '[Any]'; add explicit type annotation if this is intentional
var descriptors = [SortDescriptor(\Person.age), SortDescriptor(\Person.name)]

The problem here is that SortDescriptor is generic on both T and V, but you only want it to be generic on T. That is, you want a SortDescriptor<Person>, because you care that it compares Person. You don't want a SortDescriptor<Person, String>, because once it's created, you don't care that it's comparing on some String property of Person.
Probably the easiest way to “hide” the V is by using a closure to wrap the key path:
struct SortDescriptor<T> {
var ascending: Bool
var primitiveCompare: (T, T) -> Bool
init<V: Comparable>(keyPath: KeyPath<T, V>, ascending: Bool = true) {
primitiveCompare = { $0[keyPath: keyPath] < $1[keyPath: keyPath] }
self.ascending = ascending
}
func compare(_ a: T, _ b: T) -> Bool {
return ascending ? primitiveCompare(a, b) : primitiveCompare(b, a)
}
}
var descriptors = [SortDescriptor(keyPath: \Person.name), SortDescriptor(keyPath: \.age)]
// Inferred type: [SortDescriptor<Person>]
After that, you may want a convenient way to use a sequence of SortDescriptor to compare to objects. For that, we'll need a protocol:
protocol Comparer {
associatedtype Compared
func compare(_ a: Compared, _ b: Compared) -> Bool
}
extension SortDescriptor: Comparer { }
And then we can extend Sequence with a compare method:
extension Sequence where Element: Comparer {
func compare(_ a: Element.Compared, _ b: Element.Compared) -> Bool {
for comparer in self {
if comparer.compare(a, b) { return true }
if comparer.compare(b, a) { return false }
}
return false
}
}
descriptors.compare(jim, bob)
// false
If you're using a newer version of Swift with conditional conformances, you should be able to conditionally conform Sequence to Comparer by changing the first line of the extension to this:
extension Sequence: Comparer where Element: Comparer {

Expanding on #Rob Mayoff's answer, here's a full sorting solution
enum SortDescriptorComparison {
case equal
case greaterThan
case lessThan
}
struct SortDescriptor<T> {
private let compare: (T, T) -> SortDescriptorComparison
let ascending : Bool
init<V: Comparable>(_ keyPath: KeyPath<T,V>, ascending:Bool = true) {
self.compare = {
let v1 = $0[keyPath: keyPath]
let v2 = $1[keyPath: keyPath]
if v1 == v2 {
return .equal
} else if v1 > v2 {
return .greaterThan
} else {
return .lessThan
}
}
self.ascending = ascending
}
func compare(v1:T, v2:T) -> SortDescriptorComparison {
return compare(v1, v2)
}
}
extension Array {
mutating func sort(sortDescriptor: SortDescriptor<Element>) {
self.sort(sortDescriptors: [sortDescriptor])
}
mutating func sort(sortDescriptors: [SortDescriptor<Element>]) {
self.sort() {
for sortDescriptor in sortDescriptors {
switch sortDescriptor.compare(v1: $0, v2: $1) {
case .equal:
break
case .greaterThan:
return !sortDescriptor.ascending
case .lessThan:
return sortDescriptor.ascending
}
}
return false
}
}
}
extension Sequence {
func sorted(sortDescriptor: SortDescriptor<Element>) -> [Element] {
return self.sorted(sortDescriptors: [sortDescriptor])
}
func sorted(sortDescriptors: [SortDescriptor<Element>]) -> [Element] {
return self.sorted() {
for sortDescriptor in sortDescriptors {
switch sortDescriptor.compare(v1: $0, v2: $1) {
case .equal:
break
case .greaterThan:
return !sortDescriptor.ascending
case .lessThan:
return sortDescriptor.ascending
}
}
return false
}
}
}
struct Person {
let name : String
let age : Int
}
let jim = Person(name: "Jim", age: 25)
let bob = Person(name: "Bob", age: 30)
let tim = Person(name: "Tim", age: 25)
let abe = Person(name: "Abe", age: 20)
let people = [tim, jim, bob, abe]
let sorted = people.sorted(sortDescriptors: [SortDescriptor(\Person.age), SortDescriptor(\Person.name)])
print(sorted) //Abe, Jim, Time, Bob

Here's an almost purely functional solution:
// let's add some semantics
typealias SortDescriptor<T> = (T, T) -> Bool
// type constructor for SortDescriptor
func sortDescriptor<T, U: Comparable>(keyPath: KeyPath<T, U>, ascending: Bool) -> SortDescriptor<T> {
return { ascending == ($0[keyPath: keyPath] < $1[keyPath: keyPath]) }
}
// returns a function that can sort any two element of type T, based on
// the provided list of descriptors
func compare<T>(with descriptors: [SortDescriptor<T>]) -> (T, T) -> Bool {
func innerCompare(descriptors: ArraySlice<SortDescriptor<T>>, a: T, b: T) -> Bool {
guard let descriptor = descriptors.first else { return false }
if descriptor(a, b) { return true }
else if descriptor(b, a) { return false }
else { return innerCompare(descriptors: descriptors.dropFirst(1), a: a, b: b) }
}
return { a, b in innerCompare(descriptors: descriptors[0...], a: a, b: b) }
}
// back to imperative, extend Sequence to allow sorting with descriptors
extension Sequence {
func sorted(by descriptors: [SortDescriptor<Element>]) -> [Element] {
return sorted(by: compare(with: descriptors))
}
}
It's based on small, reusable functions, like compare(), that can be easily reused in other scopes.
Usage example:
struct Person {
let name : String
let age : Int
}
let jim = Person(name: "Jim", age: 30)
let bob = Person(name: "Bob", age: 35)
let alice = Person(name: "Alice", age: 35)
let aly = Person(name: "Aly", age: 32)
let descriptors = [sortDescriptor(keyPath: \Person.age, ascending: false),
sortDescriptor(keyPath: \Person.name, ascending: true)]
let persons = [jim, bob, alice, aly]
print(persons.sorted(by: descriptors))

Related

Swift: Lazily encapsulating chains of map, filter, flatMap

I have a list of animals:
let animals = ["bear", "dog", "cat"]
And some ways to transform that list:
typealias Transform = (String) -> [String]
let containsA: Transform = { $0.contains("a") ? [$0] : [] }
let plural: Transform = { [$0 + "s"] }
let double: Transform = { [$0, $0] }
As a slight aside, these are analogous to filter (outputs 0 or 1 element), map (exactly 1 element) and flatmap (more than 1 element) respectively but defined in a uniform way so that they can be handled consistently.
I want to create a lazy iterator which applies an array of these transforms to the list of animals:
extension Array where Element == String {
func transform(_ transforms: [Transform]) -> AnySequence<String> {
return AnySequence<String> { () -> AnyIterator<String> in
var iterator = self
.lazy
.flatMap(transforms[0])
.flatMap(transforms[1])
.flatMap(transforms[2])
.makeIterator()
return AnyIterator {
return iterator.next()
}
}
}
}
which means I can lazily do:
let transformed = animals.transform([containsA, plural, double])
and to check the result:
print(Array(transformed))
I'm pleased with how succinct this is but clearly:
.flatMap(transforms[0])
.flatMap(transforms[1])
.flatMap(transforms[2])
is an issue as it means the transform function will only work with an array of 3 transforms.
Edit:
I tried:
var lazyCollection = self.lazy
for transform in transforms {
lazyCollection = lazyCollection.flatMap(transform) //Error
}
var iterator = lazyCollection.makeIterator()
but on the marked row I get error:
Cannot assign value of type 'LazyCollection< FlattenCollection< LazyMapCollection< Array< String>, [String]>>>' to type 'LazyCollection< Array< String>>'
which I understand because each time around the loop another flatmap is being added, so the type is changing.
How can I make the transform function work with an array of any number of transforms?
One WET solution for a limited number of transforms would be (but YUK!)
switch transforms.count {
case 1:
var iterator = self
.lazy
.flatMap(transforms[0])
.makeIterator()
return AnyIterator {
return iterator.next()
}
case 2:
var iterator = self
.lazy
.flatMap(transforms[0])
.flatMap(transforms[1])
.makeIterator()
return AnyIterator {
return iterator.next()
}
case 3:
var iterator = self
.lazy
.flatMap(transforms[0])
.flatMap(transforms[1])
.flatMap(transforms[2])
.makeIterator()
return AnyIterator {
return iterator.next()
}
default:
fatalError(" Too many transforms!")
}
Whole code:
let animals = ["bear", "dog", "cat"]
typealias Transform = (String) -> [String]
let containsA: Transform = { $0.contains("a") ? [$0] : [] }
let plural: Transform = { [$0 + "s"] }
let double: Transform = { [$0, $0] }
extension Array where Element == String {
func transform(_ transforms: [Transform]) -> AnySequence<String> {
return AnySequence<String> { () -> AnyIterator<String> in
var iterator = self
.lazy
.flatMap(transforms[0])
.flatMap(transforms[1])
.flatMap(transforms[2])
.makeIterator()
return AnyIterator {
return iterator.next()
}
}
}
}
let transformed = animals.transform([containsA, plural, double])
print(Array(transformed))
You can apply the transformations recursively if you define the method on the Sequence protocol (instead of Array). Also the constraint where Element == String is not needed if the transformations parameter is defined as an array of (Element) -> [Element].
extension Sequence {
func transform(_ transforms: [(Element) -> [Element]]) -> AnySequence<Element> {
if transforms.isEmpty {
return AnySequence(self)
} else {
return lazy.flatMap(transforms[0]).transform(Array(transforms[1...]))
}
}
}
Another approach to achieve what you want:
Edit: I tried:
var lazyCollection = self.lazy
for transform in transforms {
lazyCollection = lazyCollection.flatMap(transform) //Error
}
var iterator = lazyCollection.makeIterator()
You were very near to your goal, if the both types in the Error line was assignable, your code would have worked.
A little modification:
var lazySequence = AnySequence(self.lazy)
for transform in transforms {
lazySequence = AnySequence(lazySequence.flatMap(transform))
}
var iterator = lazySequence.makeIterator()
Or you can use reduce here:
var transformedSequence = transforms.reduce(AnySequence(self.lazy)) {sequence, transform in
AnySequence(sequence.flatMap(transform))
}
var iterator = transformedSequence.makeIterator()
Whole code would be:
(EDIT Modified to include the suggestions from Martin R.)
let animals = ["bear", "dog", "cat"]
typealias Transform<Element> = (Element) -> [Element]
let containsA: Transform<String> = { $0.contains("a") ? [$0] : [] }
let plural: Transform<String> = { [$0 + "s"] }
let double: Transform<String> = { [$0, $0] }
extension Sequence {
func transform(_ transforms: [Transform<Element>]) -> AnySequence<Element> {
return transforms.reduce(AnySequence(self)) {sequence, transform in
AnySequence(sequence.lazy.flatMap(transform))
}
}
}
let transformed = animals.transform([containsA, plural, double])
print(Array(transformed))
How about fully taking this into the functional world? For example using (dynamic) chains of function calls, like filter(containsA) | map(plural) | flatMap(double).
With a little bit of reusable generic code we can achieve some nice stuff.
Let's start with promoting some sequence and lazy sequence operations to free functions:
func lazy<S: Sequence>(_ arr: S) -> LazySequence<S> {
return arr.lazy
}
func filter<S: Sequence>(_ isIncluded: #escaping (S.Element) throws -> Bool) -> (S) throws -> [S.Element] {
return { try $0.filter(isIncluded) }
}
func filter<L: LazySequenceProtocol>(_ isIncluded: #escaping (L.Elements.Element) -> Bool) -> (L) -> LazyFilterSequence<L.Elements> {
return { $0.filter(isIncluded) }
}
func map<S: Sequence, T>(_ transform: #escaping (S.Element) throws -> T) -> (S) throws -> [T] {
return { try $0.map(transform) }
}
func map<L: LazySequenceProtocol, T>(_ transform: #escaping (L.Elements.Element) -> T) -> (L) -> LazyMapSequence<L.Elements, T> {
return { $0.map(transform) }
}
func flatMap<S: Sequence, T: Sequence>(_ transform: #escaping (S.Element) throws -> T) -> (S) throws -> [T.Element] {
return { try $0.flatMap(transform) }
}
func flatMap<L: LazySequenceProtocol, S: Sequence>(_ transform: #escaping (L.Elements.Element) -> S) -> (L) -> LazySequence<FlattenSequence<LazyMapSequence<L.Elements, S>>> {
return { $0.flatMap(transform) }
}
Note that the lazy sequences counterparts are more verbose that the regular Sequence ones, but this is due to the verbosity of LazySequenceProtocol methods.
With the above we can create generic functions that receive arrays and return arrays, and this type of functions are extremely fitted for pipelining, so let's define a pipeline operator:
func |<T, U>(_ arg: T, _ f: (T) -> U) -> U {
return f(arg)
}
Now all we need is to feed something to these functions, but to achieve this we'll need a little bit of tweaking over the Transform type:
typealias Transform<T, U> = (T) -> U
let containsA: Transform<String, Bool> = { $0.contains("a") }
let plural: Transform<String, String> = { $0 + "s" }
let double: Transform<String, [String]> = { [$0, $0] }
With all the above in place, things get easy and clear:
let animals = ["bear", "dog", "cat"]
let newAnimals = lazy(animals) | filter(containsA) | map(plural) | flatMap(double)
print(Array(newAnimals)) // ["bears", "bears", "cats", "cats"]

Iterator returning Generic Collection - Swift 4.2

I can write Iterators as follows:
enum Stage { case a, ab, end }
struct SetMaker<Input: Hashable>: Sequence, IteratorProtocol {
var a,b: Input
var stage = Stage.a
init(a: Input, b: Input) {
self.a = a
self.b = b
}
mutating func next() -> Set<Input>? {
switch stage {
case .a: stage = .ab; return Set<Input>([a])
case .ab: stage = .end; return Set<Input>([a,b])
case .end: return nil
}
}
}
let setMaker = SetMaker(a: "A", b: "B")
for x in setMaker {
print(x)
}
struct ArrayMaker<Input: Hashable>: Sequence, IteratorProtocol {
var a: Input
var b: Input
var stage = Stage.a
init(a: Input, b: Input) {
self.a = a
self.b = b
}
mutating func next() -> Array<Input>? {
switch stage {
case .a: stage = .ab; return Array<Input>([a])
case .ab: stage = .end; return Array<Input>([a,b])
case .end: return nil
}
}
}
let arrayMaker = ArrayMaker(a: "A", b: "B")
for x in arrayMaker {
print(x)
}
The first returning a sequence of Sets and the second returning a sequence of Arrays.
These both work fine but I like to keep my code "DRY" (i.e. Don't Repeat Yourself).
So I'd like to do write something Generic that will allow construction of either.
My attempt is:
struct AnyMaker<Input: Hashable, CollectionType>: Sequence, IteratorProtocol {
var a,b: Input
var stage = Stage.a
init(a: Input, b: Input) {
self.a = a
self.b = b
}
mutating func next() -> CollectionType<Input>? {
switch stage {
case .a: stage = .ab; return CollectionType<Input>([a])
case .ab: stage = .end; return CollectionType<Input>([a,b])
case .end: return nil
}
}
}
But this doesn't compile.
Any help appreciated :-)
Edit...
#Rob made a good suggestion which gets me part way there - see his answer.
But if I want the Collection to be a Set sometimes then there's an issue because Set is not RangeReplaceable.
To put it another way I've created a slightly different bit of code:
struct Pairs<C>: Sequence, IteratorProtocol
where C: RangeReplaceableCollection {
var collection: C
var index: C.Index
init(_ collection: C) {
self.collection = collection
index = self.collection.startIndex
}
mutating func next() -> C? {
guard index < collection.endIndex else { return nil }
let element1 = collection[index]
index = collection.index(after: index)
guard index < collection.endIndex else { return nil }
let element2 = collection[index]
let pair = [element1,element2]
return C(pair)
}
}
do {
print("Pairs from array")
let array = ["A","B","C"]
let pairs = Pairs(array) //This line is fine
for pair in pairs {
print(pair)
}
}
do {
print("Pairs from set")
let set = Set(["A","B","C"])
let pairs = Pairs(set) // This line causes error
for pair in pairs {
print(pair)
}
}
The line "let pairs = Pairs(set)" generates an error:
"Argument type 'Set' does not conform to expected type 'RangeReplaceableCollection'"
So I need to work out how to instantiate a collection without using RangeReplaceableCollection?
You never restricted the type of CollectionType, so Swift doesn't know you can create one at all, let alone create one by passing an array. Collection itself doesn't promise any init methods, either. We need to go to RangeReplaceableCollection to get that:
struct AnyMaker<CollectionType>: Sequence, IteratorProtocol
where CollectionType: RangeReplaceableCollection {
typealias Input = CollectionType.Element
...
}
Once you've done that, next() looks like this:
mutating func next() -> CollectionType? {
switch stage {
case .a: stage = .ab; return CollectionType([a])
case .ab: stage = .end; return CollectionType([a,b])
case .end: return nil
}
}
Note that this returns CollectionType? not CollectionType<Input>?. Nothing about CollectionType requires that it take a type parameter, so we can't pass one. There's no way to express "takes a type parameter" even if we wanted it, but we don't want it. CollectionType just has to have some Element, and that's promised by RangeReplaceableCollection.
let anyMaker = AnyMaker<[String]>(a: "A", b: "B")
for x in arrayMaker {
print(x)
}
Full code:
struct AnyMaker<CollectionType>: Sequence, IteratorProtocol
where CollectionType: RangeReplaceableCollection {
typealias Input = CollectionType.Element
var a,b: Input
var stage = Stage.a
init(a: Input, b: Input) {
self.a = a
self.b = b
}
mutating func next() -> CollectionType? {
switch stage {
case .a: stage = .ab; return CollectionType([a])
case .ab: stage = .end; return CollectionType([a,b])
case .end: return nil
}
}
}
Even though Collection doesn't require an initialiser, Array and Set do have the initialiser we want:
init<S : Sequence>(_ elements: S) where S.Element == Element
so by creating a new procotol which requires this and then extending Array and Set with this protocol we can then assume that UsableCollections will have this initialiser and all works as required:
protocol UsableCollection: Collection {
init<S : Sequence>(_ elements: S) where S.Element == Element
}
extension Array: UsableCollection { }
extension Set: UsableCollection { }
struct Pairs<C: UsableCollection>: Sequence, IteratorProtocol {
var collection: C
var index: C.Index
init(_ collection: C) {
self.collection = collection
index = self.collection.startIndex
}
mutating func next() -> C? {
guard index < collection.endIndex else { return nil }
let element1 = collection[index]
index = collection.index(after: index)
guard index < collection.endIndex else { return nil }
let element2 = collection[index]
let pair = [element1,element2]
return C(pair)
}
}
do {
print("Pairs from array")
let array = ["A","B","C"]
let pairs = Pairs(array)
for pair in pairs {
print(pair)
}
}
do {
print("Pairs from set")
let set = Set(["A","B","C"])
let pairs = Pairs(set)
for pair in pairs {
print(pair)
}
}

Filtering an array based on a Swift enum with an associated value - w/o mentioning the associated value

I have an enum with associated value for some of the cases:
enum Foo {
case a
case b
case c(String?)
}
I also have a struct with this enum as a variable
struct Bar {
var foo: Foo
}
I then have an array of this objects
let array:[Bar] = [Bar(foo: .a), Bar(foo: .c(nil)), Bar(foo: .c("someString"))]
I want to create a function that operates on a subset of this array, based on the cases it receives something like
func printOnly(objectsWithCase: Foo)
So far its pretty simple, but now here's the catch: for this operation I WANT TO IGNORE the associated value.
I would like to make this function be able to take .c case without mentioning an associated value, as if to say "give me the ones with .c regardless of the associated values".
I other words - I'd like to pass in something like .c(_) (this doesn't work of course) and have it return (print in this case) both Bar(foo: .c(nil)) and Bar(foo: .c("someString"))
So far, I only came up with changing the functions declaration to take the filtering closure instead of the cases like this:
func printArray(array: [Bar], condition: (Bar) -> Bool) {
let tmp = array.filter(condition)
print(tmp)
}
I'm wondering if there's a way to do this in Swift, while passing the cases and not the condition block ?
You can use the underscore as a wild card in pattern matching operations:
array.filter {
switch $0.foo {
case .a: return true // keep a
case .b: return false // reject b
case .c(_): return true // keep c, regardless of assoc. value.
}
}
While this is not technically what you ask for (I don't think there's any way to achieve this with enums), you can write a "fake" enum that contains a wildcard c that will match anything you want. This will give you the exact same syntax.
1) Replace Foo with the following
struct Foo: Equatable {
let rawValue: String
let associatedObject: String?
let isWildcard: Bool
fileprivate init(rawValue: String, associatedObject: String?, isWildcard: Bool) {
self.rawValue = rawValue
self.associatedObject = associatedObject
self.isWildcard = isWildcard
}
static var a: Foo {
return Foo(rawValue: "a", associatedObject: nil, isWildcard: false)
}
static var b: Foo {
return Foo(rawValue: "b", associatedObject: nil, isWildcard: false)
}
static var c: Foo {
return Foo(rawValue: "c", associatedObject: nil, isWildcard: true)
}
static func c(_ value: String?) -> Foo {
return Foo(rawValue: "c", associatedObject: value, isWildcard: false)
}
}
func ==(left: Foo, right: Foo) -> Bool {
// Match rawValue + associatedObject unless we have a wildcard
return (left.rawValue == right.rawValue)
&& (left.associatedObject == right.associatedObject || left.isWilcard || right.isWildcard)
}
2) Implement your printOnly function with ==
func printOnly(objects: [Bar], with match: Foo) {
objects.filter { $0.foo == match }.forEach { print($0) }
}
3) Success
printOnly(objects: array, with: .c) // [.c(nil), .c("someString")]
Discussion
The main drawback of this method, besides the additional boilerplate code, is that you are forced to create an enum value that should not be allowed. This method puts the responsibility on you to use it only as a wildcard, and not as a real enum value. It will also not guarantee you that other enum cases cannot be created, although you should be able to mitigate that by making the only initializer fileprivate.
Otherwise, this gives you exactly the same interface and features an enum would give you, you can define your cases just as before
let array = [Bar(foo: .a), Bar(foo: .c(nil)), Bar(foo: .c("Hello")]
Finally, you can still use it inside a switch, except you will always need to add a default statement.
switch Foo.c("Hello") {
case .a:
print("A")
case .b:
print("B")
case .c: // will match .c(nil) and .c("someString")
print("C")
default:
break
}
//: Playground - noun: a place where people can play
enum Foo {
case a
case b
case c(String?)
}
struct Bar {
var foo: Foo
}
let array:[Bar] = [Bar(foo: .a), Bar(foo: .c(nil)), Bar(foo: .c("someString"))]
func printArray(array: [Bar], condition: (Bar) -> Bool) {
let tmp = array.filter(condition)
print(tmp)
}
printArray(array: array) { bar in
switch bar.foo {
case .c:
return true
default:
return false
}
}
or
printArray(array: array) { bar in
if case let Foo.c = bar.foo {
return true
}
return false
}
EDIT
//: Playground - noun: a place where people can play
enum Foo: Equatable {
case a
case b
case c(String?)
}
func ==(lhs: Foo, rhs: Foo) -> Bool {
switch (lhs, rhs) {
case (.a, .a), (.b, .b), (.c, .c):
return true
default:
return false
}
}
struct Bar {
var foo: Foo
}
let array:[Bar] = [Bar(foo: .a), Bar(foo: .c(nil)), Bar(foo: .c("someString"))]
func printArray(array: [Bar], condition: (Bar) -> Bool) {
let tmp = array.filter(condition)
print(tmp)
}
func printOnly(objectsWithCase wantedCase: Foo) {
printArray(array: array) { bar in
if wantedCase == bar.foo {
return true
} else {
return false
}
}
}
printOnly(objectsWithCase:.c(nil))

Generic way to do math on protocol extensions

Goal
I want to extend basic types like Int, Double, Float... with more flexible properties and make it presentable in a chart on my app. For example, I made a chart draw that is suitable only for displaying Intbut cannot really display Float. I want to make sure when I pass arguments to this view it will display correctly.
Solution
So I made a protocol (for this example made it like this):
protocol SimplyChartable {
static func max(_ dataSet: [SimplyChartable]) -> SimplyChartable
}
And then make an extension for some types:
extension Int: SimplyChartable { }
extension Double: SimplyChartable { }
extension Float: SimplyChartable { }
and so on ...
Problem
This will be all numeric types, and whenever I pass it as numeric types to a func I need to extend all extension like this:
public static func max(_ dataSet: [SimplyChartable]) -> SimplyChartable {
return (dataSet as? [Int])?.max() ?? 0
}
But for Double func will be identical.
So for min I will end up with similar function, the same for divide, adding , some other math... There is a way to write it once and reuse for every type that extends this protocol?
I found out that:
let dataType = type(of: maxValue) /* where `maxValue` is SimplyChartable*/
Will return original type as rawValue. But output of a method type(of is a Metatype and I cannot return it from function and then add two values of this type. So for example this code will not work:
let val1 = SimplyChartable(4)
let val2 = SimplyChartable(2)
let sum = val1 + val2
And how to make it work not ending up with 3 functions like this:
let val1 = SimplyChartable(4)
let val2 = SimplyChartable(2)
let sum = (val1 as! Int) + (val2 as! Int)
Since they all numeric types why don't you use Comparable?
extension SimplyChartable {
static func max<T: Comparable>(dataSet: [T]) -> T? {
return dataSet.max()
}
static func min<T: Comparable>(dataSet: [T]) -> T? {
return dataSet.min()
}
}
extension Int: SimplyChartable { }
extension Double: SimplyChartable { }
Double.max([1.2, 1.1, 1.3]) // 1.3
Int.min([12, 11, 13]) // 11
Just my two cents worth...
This isn't exactly what you've asked for, since it doesn't let you call a static function directly from a protocol metatype. But since that, AFAIK, isn't possible in Swift currently, perhaps this would be the next best thing?
extension Sequence where Element == SimplyChartable {
func max() -> SimplyChartable {
// put your implementation here
}
}
You can then call this by just:
let arr: [SimplyChartable] = ...
let theMax = arr.max()
For your situation, it's much better to use an Array extension rather than a protocol with an array parameter.
To handle each possible type of array i.e [Int], [Double] or [Float], create a wrapper enum with associated types as follows:
public enum SimplyChartableType {
case int(Int)
case float(Float)
case double(Double)
func getValue() -> NSNumber {
switch self {
case .int(let int):
return NSNumber(value: int)
case .float(let float):
return NSNumber(value: float)
case .double(let double):
return NSNumber(value: double)
}
}
init(int: Int) {
self = SimplyChartableType.int(int)
}
init(float: Float) {
self = SimplyChartableType.float(float)
}
init(double: Double) {
self = SimplyChartableType.double(double)
}
}
You can extend Array as follows:
extension Array where Element == SimplyChartableType {
func max() -> SimplyChartableType {
switch self[0] {
case .int(_):
let arr = self.map({ $0.getValue().intValue })
return SimplyChartableType(int: arr.max()!)
case .double(_):
let arr = self.map({ $0.getValue().doubleValue })
return SimplyChartableType(double: arr.max()!)
case .float(_):
let arr = self.map({ $0.getValue().floatValue })
return SimplyChartableType(float: arr.max()!)
}
}
}
Example usage is:
var array = [SimplyChartableType.double(3),SimplyChartableType.double(2),SimplyChartableType.double(4)]
var max = array.max()
And now it's a lot easier to operate on Int, Double or Float together with:
extension SimplyChartableType: SimplyChartable {
//insert functions here
static func randomFunction() -> SimplyChartableType {
//perform logic here
}
}
The above snippet is good if you need a different functionality which operates on non-Collection types.
This doesn't answer your specific question, unfortunately. Perhaps a work around to use a free function and casting.
import UIKit
protocol SimplyChartable {
func chartableValue() -> Double
}
extension Int: SimplyChartable {
func chartableValue() -> Double {
return Double(self) ?? 0
}
}
extension Double: SimplyChartable {
func chartableValue() -> Double {
return self
}
}
extension Float: SimplyChartable {
func chartableValue() -> Double {
return Double(self) ?? 0
}
}
func maxOfSimplyChartables(_ dataSet: [SimplyChartable]) -> SimplyChartable {
return dataSet.max(by: { (lhs, rhs) -> Bool in
return lhs.chartableValue() < rhs.chartableValue()
}) ?? 0
}
let chartableItem1: SimplyChartable = 1255555.4
let chartableItem2: SimplyChartable = 24422
let chartableItem3: SimplyChartable = 35555
let simplyChartableValues = [chartableItem1, chartableItem2, chartableItem3]
maxOfSimplyChartables(simplyChartableValues)

How to check if two [String: Any] are identical?

Is there any way to check if two [String: Any] are identical ?
let actual: [[String: Any]] = [
["id": 12345, "name": "Rahul Katariya"],
["id": 12346, "name": "Aar Kay"]
]
var expected: [[String: Any]]!
if actual == expected {
print("Equal")
}
Basically i want Dictionary to conform to Equatable protocol in Swift 3.
For Xcode 7.3, swift 2.2
A dictionary is of type : [String:AnyObject] or simply put NSDictionary
let actual: [String: AnyObject] = ["id": 12345, "name": "Rahul Katariya"]
var expected: [String: AnyObject] = ["id": 12346, "name": "Aar Kay"]
print(NSDictionary(dictionary: actual).isEqualToDictionary(expected))//False
For Xcode 8.beta 6, Swift 3
Dictionary is defined as:
struct Dictionary<Key : Hashable, Value> : Collection, ExpressibleByDictionaryLiteral
NSDictionary has the following convenience initializer:
convenience init(dictionary otherDictionary: [AnyHashable : Any])
So you can use AnyHashable type for Key and Any type for Value
let actual: [String: Any] = ["id": 12345, "name": "Rahul Katariya"]
var expected: [String: Any] = ["id": 12346, "name": "Aar Kay"]
print(NSDictionary(dictionary: actual).isEqual(to: expected))//False
Conformance to Equatable aside; for the exercise you could write your own isEqual function to compare two [T: Any] dictionaries for a subset of (Equatable) types that you know the value wrapped by Any is limited to. By attempted conversion to these types (e.g. in a switch statement, as below), you could compare the dictionary's values (for each given key) one by one after their conversion to these given types. E.g.
// Usable if the 'Any' values in your dict only wraps
// a few different types _that are known to you_.
// Return false also in case value cannot be successfully
// converted to some known type. This might yield a false negative.
extension Dictionary where Value: Any {
func isEqual(to otherDict: [Key: Any],
allPossibleValueTypesAreKnown: Bool = false) -> Bool {
guard allPossibleValueTypesAreKnown &&
self.count == otherDict.count else { return false }
for (k1,v1) in self {
guard let v2 = otherDict[k1] else { return false }
switch (v1, v2) {
case (let v1 as Double, let v2 as Double) : if !(v1.isEqual(to: v2)) { return false }
case (let v1 as Int, let v2 as Int) : if !(v1==v2) { return false }
case (let v1 as String, let v2 as String): if !(v1==v2) { return false }
// ... fill in with types that are known to you to be
// wrapped by the 'Any' in the dictionaries
default: return false
}
}
return true
}
}
Usage:
/* example setup */
var dict1: [String: Any] = ["id": 12345, "name": "Rahul Katariya", "weight": 70.7]
var dict2: [String: Any] = ["id": 12346, "name": "Aar Kay", "weight": 83.1]
/* example usage */
print(dict1.isEqual(to: dict2, allPossibleValueTypesAreKnown: true))
// false
dict2["name"] = "Rahul Katariya"
dict2["weight"] = 70.7
print(dict1.isEqual(to: dict2, allPossibleValueTypesAreKnown: true))
// false
dict2["id"] = 12345
print(dict1.isEqual(to: dict2, allPossibleValueTypesAreKnown: true))
// true
class Foo {}
dict1["id"] = Foo()
dict2["id"] = Foo()
print(dict1.isEqual(to: dict2, allPossibleValueTypesAreKnown: true))
// false! (we haven't implemented this attempted conversion!)
// incompatable keys cause error as expected an intended
let dict3: [Int: Any] = [1:2]
dict1.isEqual(to: dict3)
/* error: cannot convert value of type '[Int : Any]'
to expected argument type '[String : Any]' */
Just note the danger that the as conversion may yield a false positive (true) as it can allow mapping from two different types to a common other type, e.g. slicing away derived class differences when casting two derived class instances to their common parent type:
class Base: Equatable {}
func ==(lhs: Base, rhs: Base) -> Bool { return true }
class DerivedA : Base {
let foo = "foo"
}
class DerivedB : Base {
let bar = 4.2
}
let a = DerivedA()
let b = DerivedB()
switch (a, b) {
case (let a as Base, let b as Base): print(a == b)
default: ()
} // sliced by conversion! prints "true"
If you'd rather like a failed "known types conversion" to return nil (whereas successful conversions will always yield true/false, based on subsequent equality testing), you could extend the above to (the even messier)
// a 'nil' return here would correspond to an invalid call
extension Dictionary where Value: Any {
func isEqual(to otherDict: [Key: Any],
allPossibleValueTypesAreKnown: Bool = false) -> Bool? {
guard allPossibleValueTypesAreKnown else { return nil }
guard self.count == otherDict.count else { return false }
for (k1,v1) in self {
guard let v2 = otherDict[k1] else { return false }
switch (v1, v2) {
case (let v1 as Double, let v2 as Double) : if !(v1.isEqual(to: v2)) { return false }
case (let v1 as Int, let v2 as Int) : if !(v1==v2) { return false }
case (let v1 as String, let v2 as String): if !(v1==v2) { return false }
// ...
case (_ as Double, let v2): if !(v2 is Double) { return false }
case (_, _ as Double): return false
case (_ as Int, let v2): if !(v2 is Int) { return false }
case (_, _ as Int): return false
case (_ as String, let v2): if !(v2 is String) { return false }
case (_, _ as String): return false
default: return nil
}
}
return true
}
}
/* Example as per above will yield (printout):
Optional(false)
Optional(false)
Optional(true)
nil */
Note however that the value by value equality testing above is short-circuited in case of a false hit, which mean that depending on the random order of the non-ordered dictionaries (non-ordered collection), a special case may return nil as well as false, given two non-equal dictionaries. This special case occurs for two dictionary of non-equal values (non-equality for a known type value-value pair) which also hold an value type not included in the attempted casting: if the non-equality of known types is hit first, false will be returned, whereas if a failed conversion is hit first, nil will be returned. Either way, a nil return means the call should be considered invalid, as caller stated that allPossibleValueTypesAreKnown was true (which a failed conversion implies is false).
The type Any is not Equatable in Swift, so any collection types including Any cannot be Equatable.
You can write something like this in Swift 3/Xcode 8 beta 6:
if actual as NSArray == expected as NSArray {
print("Equal")
}
But, as importing id as Any is just introduced in beta 6, so this behaviour may change in the near future.
With Swift 5.5 you can easily cast it to NSDictionary, as it always succeeds:
XCTAssertEqual(actual as NSDictionary, expected as NSDictionary)