How do I implement Swift's Comparable protocol? - compare

How do I use the Comparable protocol in Swift? In the declaration it says I'd have to implement the three operations <, <= and >=. I put all those in the class but it doesn't work. Also do I need to have all three of them? Because it should be possible to deduce all of them from a single one.

The Comparable protocol extends the Equatable protocol -> implement both of them
In Apple's Reference is an example from Apple (within the Comparable protocol reference) you can see how you should do it: Don't put the operation implementations within the class, but rather on the outside/global scope. Also you only have to implement the < operator from Comparable protocol and == from Equatable protocol.
Correct example:
class Person : Comparable {
let name : String
init(name : String) {
self.name = name
}
}
func < (lhs: Person, rhs: Person) -> Bool {
return lhs.name < rhs.name
}
func == (lhs: Person, rhs: Person) -> Bool {
return lhs.name == rhs.name
}
let paul = Person(name: "Paul")
let otherPaul = Person(name: "Paul")
let ben = Person(name: "Ben")
paul > otherPaul // false
paul <= ben // false
paul == otherPaul // true

Here is an update of Kametrixom's answer for Swift 3:
class Person : Comparable {
let name : String
init(name : String) {
self.name = name
}
static func < (lhs: Person, rhs: Person) -> Bool {
return lhs.name < rhs.name
}
static func == (lhs: Person, rhs: Person) -> Bool {
return lhs.name == rhs.name
}
}
Instances of the Person class can then be compared with the relational operators as follows:
let paul = Person(name: "Paul")
let otherPaul = Person(name: "Paul")
let ben = Person(name: "Ben")
print(paul > otherPaul) // false
print(paul <= ben) // false
print(paul == otherPaul) // true

To implement Swift's Comparable protocol, you need to conform to the Equatable protocol first by implementing static func == (lhs: Self, rhs: Self) -> Bool, then implementing the only required function static func < (lhs: Self, rhs: Self) -> Bool for Comparable.
Instead of declaring global operator overloads, you should instead implement the protocol conforming methods within the struct/class itself. Although global operator overloads satisfy the protocol conformance, it's bad practice to declare them that way instead of the intended static methods on the struct/class.
If you look at the documentation example, you will see that the same is shown as sample code.
I would instead write the following:
class Person: Comparable {
let name: String
init(name: String) {
self.name = name
}
static func < (lhs: Person, rhs: Person) -> Bool {
return lhs.name < rhs.name
}
static func == (lhs: Person, rhs: Person) -> Bool {
return lhs.name == rhs.name
}
}
or even separate out the protocol conformance out of the class declaration like so:
class Person {
let name: String
init(name: String) {
self.name = name
}
}
extension Person: Comparable {
static func < (lhs: Person, rhs: Person) -> Bool {
return lhs.name < rhs.name
}
static func == (lhs: Person, rhs: Person) -> Bool {
return lhs.name == rhs.name
}
}
which would probably be closer to production level code.

Related

Heterogeneous collection

In the new versions of C++, you can check if an item is in a unordered_set (a HashSet), even if that item is not the same type as the unordered_set, whilst maintaining O(1) time complexity.
I'm trying to find out how to do this in Swift.
Here is the C++ example:
struct First {
int data;
std::string otherData;
First(int data, std::string otherData) : data(data), otherData(otherData) { }
};
struct Second {
int data;
int otherData;
Second(int data, int otherData) : data(data), otherData(otherData) { }
};
Suppose I want to create an unordered_set of First, but I want to check if a Second object is in the Set, comparing by its data field. You could do this:
struct Equal {
using is_transparent = void;
template<class F, class S>
bool operator()(const F& lhs, const S& rhs) const {
return lhs.data == rhs.data;
}
};
struct Hash {
using is_transparent = void;
template<class T>
size_t operator()(const T& t) const {
return std::hash<int>{}(t.data);
}
};
int main()
{
std::unordered_set<First, Hash, Equal> set;
set.insert(First(100, "test"));
std::cout << set.contains(First(100, "bla")) << "\n"; // true
std::cout << set.contains(Second(100, 1000)) << "\n"; // true
}
And this works great. However, I'm not sure how you would achieve this in Swift. In Swift, a Set is the same thing as unordered_set, but its contains method only accepts that specific element (no overloads).
You could iterate through all the elements, but you lose the O(1) HashSet time complexity.
I was wondering, is this possible in Swift?
To meet the basic requirement (partial matching), you can use contains(where:) with a predicate to compare the hash values of elements to the hash of the target.
class First:Hashable {
var data:Int;
var otherData:String;
static func == (lhs:First, rhs:First) -> Bool {
return lhs.data == rhs.data;
}
init(data:Int, otherData:String) {
self.data = data;
self.otherData = otherData;
}
func hash(into hasher: inout Hasher) {
hasher.combine(data)
}
};
class Second:Hashable {
var data:Int;
var otherData:Int;
static func == (lhs:Second, rhs:Second) -> Bool {
return lhs.data == rhs.data;
}
init(data:Int, otherData:Int) {
self.data = data;
self.otherData = otherData;
}
func hash(into hasher: inout Hasher) {
hasher.combine(data)
}
};
var set: Set = [First(data: 100, otherData: "test")];
print(set.contains(First(data: 100, otherData: "bla")));
var hasher = Hasher();
Second(data: 100, otherData: 1000).hash(into:&hasher);
var target = hasher.finalize();
print(set.contains(where: {(candidate:First) -> Bool in
var hasher = Hasher();
candidate.hash(into:&hasher);
return hasher.finalize() == target;
}));
To meet the performance requirement, there are (at least) two options: refactor the hashable data to a common base class, or write an extension method that creates a temporary element of the appropriate type with the hashable data.
Moving the hashable data to a base class is the most straight-forward, though the resultant Set will only be homogenous in the base class. Also, this approach can't be implemented if you don't have control over the source of the element classes.
Once the classes are defined, Set.contains(_:) will work as desired.
class Zeroth:Hashable {
var data:Int;
static func == (lhs:Zeroth, rhs:Zeroth) -> Bool {
return lhs.data == rhs.data;
}
init(_ data:Int) {
self.data = data;
}
func hash(into hasher: inout Hasher) {
hasher.combine(data)
}
};
class First:Zeroth {
var otherData:String;
init(data:Int, otherData:String) {
self.otherData = otherData;
super.init(data)
}
};
class Second:Zeroth {
var otherData:Int;
init(data:Int, otherData:Int) {
self.otherData = otherData;
super.init(data)
}
};
var test = First(data: 100, otherData: "test");
var bla = First(data: 100, otherData: "bla");
var set: Set<Zeroth> = [test];
print(set.contains(bla));
var member = Second(data: 100, otherData: 1000);
print(set.contains(member));
An extension method gets the closest to the C++ interface. Use a protocol so the extension method can be constrained to classes that only hash some of their data. The protocol used below also adds a method, partialCopy(from:), that handles converting between classes.
protocol DataElement {
var data:Int {get}
init(_ data:Int)
static func partialCopy<Other:DataElement>(from other:Other) -> Self;
}
extension DataElement {
static func partialCopy<Other:DataElement>(from other:Other) -> Self {
return Self(other.data);
}
}
class First:Hashable, DataElement {
var data:Int;
var otherData:String = "";
static func == (lhs:First, rhs:First) -> Bool {
return lhs.data == rhs.data;
}
required init(_ data:Int) {
self.data = data;
}
init(data:Int, otherData:String) {
self.data = data;
self.otherData = otherData;
}
func hash(into hasher: inout Hasher) {
hasher.combine(data)
}
};
class Second:Hashable, DataElement {
var data:Int;
var otherData:Int = 0;
static func == (lhs:Second, rhs:Second) -> Bool {
return lhs.data == rhs.data;
}
required init(_ data:Int) {
self.data = data;
}
init(data:Int, otherData:Int) {
self.data = data;
self.otherData = otherData;
}
func hash(into hasher: inout Hasher) {
hasher.combine(data)
}
};
var test = First(data: 100, otherData: "test");
var bla = First(data: 100, otherData: "bla");
var set: Set<First> = [test];
print(set.contains(bla));
extension Set where Element:DataElement {
func contains<Other:DataElement>(matching member:Other) -> Bool {
let matching : Element = Element.partialCopy(from:member); //Element(member.data);
return self.contains(matching);
}
}
var other = Second(data: 100, otherData: 1000);
print(set.contains(matching:other));
Method #1
You can use an enum to store First and Second in the same set. You will have a case for First and a case for Second.
In the Hashable conformance for the enum, you should hash the data which is the same between both structs. The Equatable conformance just makes sure that if the hashes are equal, they are equivalent, even if the enum case is different.
Example:
enum Both: Hashable {
case first(First)
case second(Second)
func hash(into hasher: inout Hasher) {
switch self {
case .first(let first):
hasher.combine(first.data)
case .second(let second):
hasher.combine(second.data)
}
}
static func == (lhs: Both, rhs: Both) -> Bool {
lhs.hashValue == rhs.hashValue
}
}
struct First {
let data: Int
let otherData: String
}
struct Second {
let data: Int
let otherData: Int
}
let set: Set<Both> = [.first(First(data: 100, otherData: "test"))]
let first = First(data: 100, otherData: "bla")
print(set.contains(.first(first))) // true
let second = Second(data: 100, otherData: 1000)
print(set.contains(.second(second))) // true
Method #2
This may not be possible, if First and Second must be a struct. However, if they don't, you can have a superclass that does the Hashable conformance.
Example:
class Superclass: Hashable {
let data: Int
init(data: Int) {
self.data = data
}
func hash(into hasher: inout Hasher) {
hasher.combine(data)
}
static func == (lhs: Superclass, rhs: Superclass) -> Bool {
lhs.data == rhs.data
}
}
class First: Superclass {
let otherData: String
init(data: Int, otherData: String) {
self.otherData = otherData
super.init(data: data)
}
}
class Second: Superclass {
let otherData: Int
init(data: Int, otherData: Int) {
self.otherData = otherData
super.init(data: data)
}
}
let set: Set<Superclass> = [First(data: 100, otherData: "test")]
let first = First(data: 100, otherData: "bla")
print(set.contains(first)) // true
let second = Second(data: 100, otherData: 1000)
print(set.contains(second)) // true

Using a kotlin clause with enums instead of multiple if

I am working with the next code:
override fun presentNativeItem(dcsItem: DCSItem?): Any {
if (dcsItem?.type == "NavMenu") {
return buildNavMenu(dcsItem)
} else if (dcsItem?.type == "NavLink") {
return buildNavLink(dcsItem)
} else if (dcsItem?.type == "Image") {
return buildImage(dcsItem)
}
else throw IllegalStateException("Unknown Type ${dcsItem?.type} of NavItem")
}
But instead of using multiple if, I would like to use the next enum:
enum class DSCType {
NAVMENU,
NAVLINK,
IMAGE;
override fun toString(): String {
return this.name.toLowerCase()
}
companion object {
fun fromString(value: String?): DSCType? {
return when (value?.toLowerCase()) {
"NavMenu" -> NAVMENU
"NavLink" -> NAVLINK
"Image" -> IMAGE
else -> null
}
}
}
}
Any ideas of how can I achieve that in the kotlin way?
Thanks
Make your input parameter not nullable and change your function to:
override fun presentNativeItem(dcsItem: DCSItem) = when(dcsItem) {
NAVMENU -> buildNavMenu(dcsItem)
NAVLINK -> buildNavLink(dcsItem)
IMAGE -> buildImage(dcsItem)
}
try this:
fun presentNativeItem(dcsItem: DCSItem?): Any {
return enumValues<DSCType>().firstOrNull { dcsItem?.type == it.typeName }
?.build(dcsItem)
?: throw IllegalStateException("Unknown Type ${dcsItem?.type} of NavItem")
}
enum class DSCType(val typeName: String) {
NAV_MENU("NavMenu") {
override fun build(dcsItem: DCSItem?): Any {
TODO("not implemented")
}
},
NAV_LINK("NavLink") {
override fun build(dcsItem: DCSItem?): Any {
TODO("not implemented")
}
},
IMAGE("Image") {
override fun build(dcsItem: DCSItem?): Any {
TODO("not implemented")
}
};
abstract fun build(dcsItem: DCSItem?): Any
}

Swift | Set with NSObject

I'm trying to create a Set with custom objects.
This is working, If I use a Set of my custom objects there is no duplicates :
public class AttributesGroup: Hashable, Equatable, Comparable {
open var id: Int!
open var name: String!
open var position: Int!
public init (id: Int = 0, name: String = "", position: Int = 0) {
self.id = id
self.name = name
self.position = position
}
open var hashValue: Int {
get {
return id.hashValue
}
}
public static func ==(lhs: AttributesGroup, rhs: AttributesGroup) -> Bool {
return lhs.id == rhs.id
}
public static func < (lhs: AttributesGroup, rhs:AttributesGroup) -> Bool {
return lhs.position < rhs.position
}
}
I extend my class with NSObject, since NSObject already implements Hashable protocol (and also Equatable) I have to override hashValue, and this is not working anymore, If I use a Set of my custom objects there is duplicates, what do I do wrong here ? :
public class AttributesGroup: NSObject, Comparable {
open var id: Int!
open var name: String!
open var position: Int!
public init (id: Int = 0, name: String = "", position: Int = 0) {
self.id = id
self.name = name
self.position = position
}
open override var hashValue: Int {
get {
return id.hashValue
}
}
public static func ==(lhs: AttributesGroup, rhs: AttributesGroup) -> Bool {
return lhs.id == rhs.id
}
public static func < (lhs: AttributesGroup, rhs:AttributesGroup) -> Bool {
return lhs.position < rhs.position
}
}
Thanks for your help !
NSObject is a Cocoa type. The rules for NSObject are different from the rules for Swift. To make an NSObject work in a set, it must have an implementation of isEqual consonant with its implementation of hash.

Delegate not being called in Swift

I can't figure out why my Delegate is not being called.
Here is where I define the protocol and call the delegate:
protocol CommentRatingViewControllerDelegate: class {
func didCommentOrRatePost(updatedRating: Bool, addedComment:Bool)
}
class CommentRatingViewController: UIViewController, UITextViewDelegate {
weak var delegate:CommentRatingViewControllerDelegate?
...
#IBAction func saveRatingComment(_ sender: Any) {
var updatedRating = false
var addedComment = false
rating = ratingView.rating
if rating != 0.0 {
saveRating(articleID: post.articleID, userID: post.userID)
updatedRating = true
}
if commentsTextView.text != "" {
saveComment(articleID: post.articleID, userID: post.userID, comment: commentsTextView.text!)
addedComment = true
}
self.delegate?.didCommentOrRatePost(updatedRating: updatedRating, addedComment: addedComment)
close()
}
....
And here is where conform to the delegate protocol:
extension PostDetailViewController: CommentRatingViewControllerDelegate {
func didCommentOrRatePost(updatedRating: Bool, addedComment: Bool) {
if updatedRating == true || addedComment == true {
networkingState = .searching
if updatedRating {
getRating(articleID: post.articleID)
}
if addedComment {
post.numberOfComments += post.numberOfComments
}
networkingState = .finishedSearching
tableView.reloadData()
}
}
}
When you conform to a protocol, if you want to call delegate methods, it is not enough to make your class conform to the protocol, you also need to set the delegate to self inside the class.
class CommentRatingViewController: UIViewController, UITextViewDelegate {
override func viewDidLoad() {
self.delegate = self
}
}

How to append Int to the new Data struct (Swift 3)

With NSMutableData I could create an array of Int's or Float's and store those to disk.
protocol BinaryConvertible
{
init()
}
extension Int : BinaryConvertible {}
struct Storage<T: BinaryConvertible>
{
let data = NSMutableData()
func append(value: T)
{
var input = value
data.append(&input, length: sizeof(T))
}
func extract(index: Int) -> T
{
var output = T()
let range = NSRange(location: index * sizeof(T), length: sizeof(T))
data.getBytes(&output, range: range)
return output
}
}
Swift 3 has a new Data type which uses NSData under the hood. Like String and NSString. I can't figure out how to add e.g. a Double using the new methods.
The append function now expects a UnsafePointer<UInt8>, but how do you create this from a Double or any random struct for that matter?
Working with pointers is one of my least favorite thing to do in Swift, but it also offer a good learning experience. This works for me:
struct Storage<T: BinaryConvertible>
{
var data = Data()
mutating func append(value: T)
{
var input = value
let buffer = UnsafeBufferPointer(start: &input, count: 1)
self.data.append(buffer)
}
func extract(index: Int) -> T
{
let startIndex = index * sizeof(T)
let endIndex = startIndex + sizeof(T)
var output = T()
let buffer = UnsafeMutableBufferPointer(start: &output, count: 1)
let _ = self.data.copyBytes(to: buffer, from: startIndex..<endIndex)
return output
}
}
var s = Storage<Double>()
s.append(value: M_PI)
s.append(value: 42)
s.append(value: 100)
print(s.extract(index: 0))
print(s.extract(index: 1))
print(s.extract(index: 2))
I like to use + or +=
public protocol DataConvertible {
static func + (lhs: Data, rhs: Self) -> Data
static func += (lhs: inout Data, rhs: Self)
}
extension DataConvertible {
public static func + (lhs: Data, rhs: Self) -> Data {
var value = rhs
let data = Data(buffer: UnsafeBufferPointer(start: &value, count: 1))
return lhs + data
}
public static func += (lhs: inout Data, rhs: Self) {
lhs = lhs + rhs
}
}
extension UInt8 : DataConvertible { }
extension UInt16 : DataConvertible { }
extension UInt32 : DataConvertible { }
extension Int : DataConvertible { }
extension Float : DataConvertible { }
extension Double : DataConvertible { }
extension String : DataConvertible {
public static func + (lhs: Data, rhs: String) -> Data {
guard let data = rhs.data(using: .utf8) else { return lhs}
return lhs + data
}
}
extension Data : DataConvertible {
public static func + (lhs: Data, rhs: Data) -> Data {
var data = Data()
data.append(lhs)
data.append(rhs)
return data
}
}
sample
var data = Data()
data += 1
data += 1.0
data += UInt8(1)
data += "1"