Let's say I have this struct:
struct TestStruct {
enum Category {
case a
case b
case c
var sortIndex: Int {
switch self {
case .a:
return 1
case .b:
return 0
case .c:
return 2
}
}
}
var category: Category
var name: String
}
I want to order a collection containing these by their category's sortIndex. Here is what I did :
let tableTest: [TestStruct] = [TestStruct(category: .b, name: "hello"),
TestStruct(category: .c, name: "hi"),
TestStruct(category: .a, name: "ok"),
TestStruct(category: .b, name: "bye")]
print(tableTest.sorted(by: { (first, second) -> Bool in
first.category.sortIndex < second.category.sortIndex
}))
hello, bye, ok, hi
It works but I want to know if there is a more elegant and fast way of writing this.
Thanks for your help.
You can use like this
tableTest.sorted(by: { $0.category.sortIndex < $1.category.sortIndex })
Just do something like this:
extension TestStruct.Category: Comparable {
static func < (lhs: TestStruct.Category, rhs: TestStruct.Category) -> Bool {
return lhs.sortIndex < rhs.sortIndex
}
}
extension TestStruct: Comparable {
static func < (lhs: TestStruct, rhs: TestStruct) -> Bool {
return lhs.category < rhs.category
}
}
and then
print(tableTest.sorted())
N.B.: TestStruct may require you to define method (static func == (::) -> Bool) of Equatable which you can write by yourself, or autoimplement it by adding conformance to Equatable in struct declaration:
struct TestStruct: Equatable {
// …
}
Related
I have an enum where each case has different (or none) associated values. The enum is not equatable.
I'm using on several places if case to check if a value is of a specific case.
enum MyEnum {
case justACase
case numberCase(Int)
case stringCase(String)
case objectCase(NonEquatableObject)
}
let myArray: [MyEnum] = [.justACase, .numberCase(100), .stringCase("Hello Enum"), .justACase]
if case .justACase = myArray[0] {
print("myArray[0] is .justACase")
}
if case .numberCase = myArray[1] {
print("myArray[1] is .numberCase")
}
But I would like to extract that into its own method where I pass on the case I want to check for. I imagine something like the following. (Please write in the comments if something is not clear.)
func checkCase(lhsCase: /*???*/, rhsCase: MyEnum) -> Bool {
if case lhsCase = rhsCase {
return true
} else {
return false
}
}
// Usage:
if checkCase(lhsCase: .justACase, rhsCase: myArray[0]) {
print("myArray[0] is .justACase")
}
In my example above lhsCase should not be of type MyEnum I assume, as this would mean I'd try to compare two properties (See code just below) and would require my enums to be equatables. Which they are not.
I'm not sure if this is even possible that way.
If the following would work it would also solve my problem but it does not.
if case myArray[3] = myArray[0] {
print("myArray[0] is .justACase")
}
The answer of Shadowrun is a good start. Through the CasePaths library I found EnumKit, which was partially the inspiration for CasePaths. However it gives much simpler methods to compare cases. See my implementation below.
Using this library comes with some nice bonuses, it allows enums that have associated values to be made equatable by just comparing the cases and ignoring the values. This might not always be desired but come in quite handy in a lot of cases. I use it to compare ReSwift Actions with Associated values in my tests.
import Foundation
import EnumKit
class NonEquatableObject {
}
enum MyEnum: CaseAccessible { // <-- conform to CaseAccessible
case justACase
case numberCase(Int)
case stringCase(String)
case objectCase(NonEquatableObject)
}
let myArray: [MyEnum] = [.justACase, .numberCase(100), .stringCase("Hello Enum"), .justACase]
if case .justACase = myArray[0] {
print("myArray[0] is .justACase")
}
if case .numberCase = myArray[1] {
print("myArray[1] is .numberCase")
}
func checkCase(lhsCase: MyEnum, rhsCase: MyEnum) -> Bool {
if case lhsCase = rhsCase {
return true
} else {
return false
}
}
// Usage:
if checkCase(lhsCase: .justACase, rhsCase: myArray[0]) { //<-- allows to pass variables or just the cases (unfortunately not without associated value.)
print("myArray[0] is .justACase")
}
if case myArray[3] = myArray[0] { //<-- allows this here too
print("myArray[0] is .justACase")
}
// Bonus: Adding equatable if associated values are not equatable. Looking at the case only.
extension MyEnum: Equatable {
static func == (lhs: MyEnum, rhs: MyEnum) -> Bool {
lhs.matches(case: rhs)
}
}
if myArray[3] == myArray[0] {
print("myArray[3] == myArray[0]")
}
There's a library CasePaths that might do what you want: something like this:
struct NonEO {
var a: Any
}
enum MyEnum {
case justACase
case numberCase(Int)
case stringCase(String)
case nonEO(NonEO)
}
let myArray: [MyEnum] = [.justACase, .nonEO(NonEO(a: 42)), .stringCase("Hello Enum"), .justACase, .nonEO(NonEO(a: "Thing"))]
func sameCase<T>(casePath: CasePath<MyEnum, T>,
lhs: MyEnum, rhs: MyEnum) -> Bool {
(casePath.extract(from: lhs) != nil)
&& casePath.extract(from: rhs) != nil
}
sameCase(casePath: /MyEnum.justACase, lhs: myArray[0], rhs: myArray[1]) // FALSE
sameCase(casePath: /MyEnum.justACase, lhs: myArray[0], rhs: myArray[3]) // TRUE
sameCase(casePath: /MyEnum.nonEO, lhs: myArray[1], rhs: myArray[4]) // TRUE
sameCase(casePath: /MyEnum.nonEO(.init(a: 42)), lhs: myArray[1], rhs: myArray[4]) // FALSE
I am trying to make a simple game implementation. So each game has a correct Answer. The Answer could be an Int or String. So what I have in code is:
protocol Answer {}
extension Int: Answer {}
extension String: Answer {}
protocol CorrectAnswer {
var correctAnswer: Answer { get }
}
I have a protocol for what a game needs:
protocol GameDescriber {
var name: String { get }
var description: String { get }
var points: Int { get }
}
And the implementation of the Game struct:
struct Game: GameDescriber, Equatable, CorrectAnswer {
var correctAnswer: Answer
var name: String
var description: String
var points: Int
static func ==(_ lhs: Game, _ rhs:Game) -> Bool {
if let _ = lhs.correctAnswer as? String, let _ = rhs.correctAnswer as? Int {
return false
}
if let _ = lhs.correctAnswer as? Int, let _ = rhs.correctAnswer as? String {
return false
}
if let lhsInt = lhs.correctAnswer as? Int, let rhsInt = rhs.correctAnswer as? Int {
if lhsInt != rhsInt {
return false
}
}
if let lhsString = lhs.correctAnswer as? String, let rhsString = rhs.correctAnswer as? String {
if lhsString != rhsString {
return false
}
}
return lhs.description == rhs.description &&
lhs.name == rhs.name &&
lhs.points == rhs.points
}
}
If I want to add another Answer type (let's say an array of Ints) I have to do that:
extension Array: Answer where Element == Int {}
But what bothers me is in the implementation of the Equatable func == I have to cover this and possibly other cases as well. Which can me dramatic :)
Is there a solution for this and can it be done in more elegant and generic way?
First note that your implementation of == can be simplified to
static func ==(_ lhs: Game, _ rhs:Game) -> Bool {
switch (lhs.correctAnswer, rhs.correctAnswer) {
case (let lhsInt as Int, let rhsInt as Int):
if lhsInt != rhsInt {
return false
}
case (let lhsString as String, let rhsString as String):
if lhsString != rhsString {
return false
}
default:
return false
}
return lhs.description == rhs.description &&
lhs.name == rhs.name &&
lhs.points == rhs.points
}
so that adding another answer type just means adding one additional
case.
The problem is that the compiler cannot verify that all possible
answer types are handled in your == function, so this approach
is error-prone.
What I actually would do is to use an enum Answer instead of a
protocol, and make that Equatable:
enum Answer: Equatable {
case int(Int)
case string(String)
}
Note that you don't have to implement ==. As of Swift 4.1, the
compiler synthesizes that automatically, see
SE-0185 Synthesizing Equatable and Hashable conformance.
And now Game simplifies to
struct Game: GameDescriber, Equatable, CorrectAnswer {
var correctAnswer: Answer
var name: String
var description: String
var points: Int
}
where the compiler synthesizes == as well, with a default implementation that compares all stored properties for equality.
Adding another answer type is simply done by adding another case to
the enumeration:
enum Answer: Equatable {
case int(Int)
case string(String)
case intArray([Int])
}
without any additional code.
I've got Workout class with Difficulty property
enum Difficulty: String {
case easy = "easy"
case moderate = "moderate"
case hard = "hard"
}
class Workout {
var name: String?
var difficulty: Difficulty?
.
.
.
}
I'd like to sort an array of workouts by the difficulty property. I know I can achieve that by assigning enum's raw value to Int value and compare these values as follows:
data.sort { $0.workout.difficulty!.rawValue < $1.workout.difficulty!.rawValue }
But I really want this enum to store string, since it's convenient to assign it to label text down the line without ugly switch-case hacks, and be comparable in some way.
How to achieve that?
Implement the Comparable protocol on your enum. It gives you a static func < (lhs: Difficulty, rhs: Difficulty) -> Bool method where you define the sort.
Here is a full sample using a property to simplify the ordering
enum Difficulty: String, Comparable {
case easy = "easy"
case moderate = "moderate"
case hard(String) = "hard"
private var sortOrder: Int {
switch self {
case .easy:
return 0
case .moderate:
return 1
case .hard(_):
return 2
}
}
static func ==(lhs: Difficulty, rhs: Difficulty) -> Bool {
return lhs.sortOrder == rhs.sortOrder
}
static func <(lhs: Difficulty, rhs: Difficulty) -> Bool {
return lhs.sortOrder < rhs.sortOrder
}
}
Making it possible to use
data.sort { $0.workout.difficulty! < $1.workout.difficulty! }
edit/update: Swift 5.1 or later
You can change your enumeration RawValue type to integer and use its rawValue to sort your Workouts. Btw you should use a structure instead of a class and similar to what was suggested by Igor you could make your struct comparable instead of the enumeration:
struct Workout {
let name: String
let difficulty: Difficulty
}
extension Workout {
enum Difficulty: Int { case easy, moderate, hard }
}
extension Workout: Comparable {
static func <(lhs: Workout, rhs: Workout) -> Bool { lhs.difficulty.rawValue < rhs.difficulty.rawValue }
}
let wk1 = Workout(name: "night", difficulty: .hard)
let wk2 = Workout(name: "morning", difficulty: .easy)
let wk3 = Workout(name: "afternoon", difficulty: .moderate)
let workouts = [wk1, wk2, wk3] // [{name "night", hard}, {name "morning", easy}, {name "afternoon", moderate}]
let sorted = workouts.sorted() // [{name "morning", easy}, {name "afternoon", moderate}, {name "night", hard}]
I have a generic protocol:
protocol SectionType {
associatedtype I: Equatable
associatedtype T: Equatable
var info: I? { get }
var items: [T] { get }
}
and an Array extension for it:
/// Offers additional method(s) to process SectionType list.
extension Array where Element: SectionType {
/// Dummy comparision method. Implementation is not that relevant but just to employ Equatable.
/// - Parameter to: Another array of sections.
/// - Returns: True if first info and first elemements in both arrays exist and both are equal.
func dummyCompare(with otherArray: [Element]) -> Bool {
guard
let first = self.first,
let firstOther = otherArray.first,
let firstElement = first.items.first,
let firstOtherElement = firstOther.items.first,
let firstInfo = first.info, let firstOtherInfo = firstOther.info
else { return false }
return firstInfo == firstOtherInfo && firstElement == firstOtherElement
}
}
No problem when using with concrete implementations:
Example 1: Works with specific implementation with build-in String type:
Declaration:
struct StringSection: SectionType {
let info: String?
let items: [String]
}
Usage:
let stringSection1 = StringSection(info: "Info 1", items: ["Item 1", "Item 2"])
let stringSection2 = StringSection(info: "Info 2", items: ["Item 3", "Item 4"])
let stringSections1 = [stringSection1, stringSection2]
let stringSections2 = [stringSection2, stringSection1]
var areEqual = stringSections1.dummyCompare(with: stringSections2)
print("String section 1 equals 2?: \(areEqual)")
Example 2 - Works with specific implementation with custom types:
Declarations:
protocol SectionInfoType {
var title: String { get }
}
/// BTW: This is just Swift's stragne way of implementing Equatable protocol for your type:
func == (lhs: SectionInfoType, rhs: SectionInfoType) -> Bool {
return lhs.title == rhs.title
}
struct SpecificSectionInfo: SectionInfoType, Equatable {
let title: String
static func == (lhs: SpecificSectionInfo, rhs: SpecificSectionInfo) -> Bool {
return lhs.title == rhs.title
}
}
protocol SectionItemType {
var text: String { get }
}
/// BTW: This is just Swift's stragne way of implementing Equatable protocol for your type:
func == (lhs: SectionItemType, rhs: SectionItemType) -> Bool {
return lhs.text == rhs.text
}
struct SpecificSectionItem: SectionItemType, Equatable {
let text: String
static func == (lhs: SpecificSectionItem, rhs: SpecificSectionItem) -> Bool {
return lhs.text == rhs.text
}
}
struct SpecificSection: SectionType {
let info: SpecificSectionInfo?
let items: [SpecificSectionItem]
}
Usage:
let specInfo1 = SpecificSectionInfo(title: "Info 1")
let specItem1 = SpecificSectionItem(text: "Specific item 1")
let specItem2 = SpecificSectionItem(text: "Specific item 2")
let specInfo2 = SpecificSectionInfo(title: "Info 2")
let specItem3 = SpecificSectionItem(text: "Specific item 3")
let specItem4 = SpecificSectionItem(text: "Specific item 4")
let specSection1 = SpecificSection(info: specInfo1, items: [specItem1, specItem2])
let specSection2 = SpecificSection(info: specInfo2, items: [specItem3, specItem4])
let specSections1 = [specSection1, specSection2]
let specSections2 = [specSection2, specSection1]
let areEqual = specSections1.dummyCompare(with: specSections2)
print("Specific section 1 equals 2?: \(areEqual)")
So far, so good, everything works and compiles. But ... I have at least 2 problems with this approach:
Problem 1:
Just from 2 examples above, one can see that this approach needs 'specific' implementation of a SectionType protocol for every combination of info and items type. This seems not so efficient (tremendous amount of code for every each implementation) nor generic.
What I need is a more generalised embodiment of SectionType protocol where types for info and items need to be protocols (provided from external APIs as protocols).
A perfect example (but does not compile):
struct Section: SectionType {
typealias I = SectionInfoType
typealias T = SectionItemType
let info: I?
let items: [T]
}
Problem 2:
I need to be able to pass it over to other protocol oriented API, f.ex.: as an argument to function like:
func consume(section: SectionType<SectionInfoType, SectionItemType>) {}
But above function declaration produces:
Cannot specialize non-generic type 'SectionType'
with Xcode proposing a fix: 'Delete <SectionInfoType, SectionItemType>' resulting in this:
func consume(section: SectionType) {}
This does not compile neither and I can not find a way to make it work.
Is it possible to do or is that Swift 3 limitation (I am using Swift 3.1)?
I created Git repository demonstrating those issues. Feel free to collaborate:
https://github.com/lukaszmargielewski/swift3-learning-generics
If I understand correctly problem 2 you want your a function that consumes a generic variable that is also specialised, adhering to protocol SectionType. Try:
func consume<Section: SectionType>(section: Section) {
let foo = section.info
let foobar = section.items
}
What I am trying to do is create a protocol extension to fetch an array of raw values from an enum. For example say I have the following:
enum TestType: String, EnumIteratable {
case unitTest = "Unit Test"
case uiTest = "UI Test"
}
class EnumIterator: NSObject {
class func iterateEnum<T: Hashable>(_: T.Type) -> AnyGenerator<T> {
var i = 0
return anyGenerator {
let next = withUnsafePointer(&i) { UnsafePointer<T>($0).memory }
return next.hashValue == i++ ? next : nil
}
}
class func getValues<T: Hashable>(_: T.Type) -> [T] {
let iterator = self.iterateEnum(T)
var returnArray = [T]()
for val in iterator {
returnArray.append(val)
}
return returnArray
}
}
How can I implement the protocol EnumIteratable so that I can call TestType.getRawValues() and have it return an string array of all the raw enum values?
Thanks!
Scott's solution is probably the one you want. But if you were looking for something more generic that you can apply to arbitrary future enumerations and allows for additional cases, you could try this:
First, you need a method to iterate over Enum cases. I used this implementation from here: https://stackoverflow.com/a/28341290/914887
func iterateEnum<T: Hashable>(_: T.Type) -> AnyGenerator<T> {
var i = 0
return anyGenerator {
let next = withUnsafePointer(&i) { UnsafePointer<T>($0).memory }
return next.hashValue == i++ ? next : nil
}
}
Then, you can create your protocol, that defines the static functions you want:
protocol EnumIteratable {
typealias ENUM_TYPE:Hashable, RawRepresentable = Self
static func getAllValues() -> [ ENUM_TYPE ]
static func getRawValues() -> [ ENUM_TYPE.RawValue ]
}
I used an associated type to allow the conforming enums to specify their type to the protocol. getAllValues is not strictly necessary, but it simplifies the logic.
Then, you can define your generic default implementations:
extension EnumIteratable {
static func getAllValues() -> [ ENUM_TYPE ]
{
var retval = [ ENUM_TYPE ]()
for item in iterateEnum( ENUM_TYPE )
{
retval.append( item )
}
return retval
}
static func getRawValues() -> [ ENUM_TYPE.RawValue ]
{
return getAllValues().map( { ( item:ENUM_TYPE ) -> ENUM_TYPE.RawValue in item.rawValue } )
}
}
Finally, all you need to do is conform to that protocol any time you need to iterate over the enum:
enum TestType: String, EnumIteratable {
case unitTest = "Unit Test"
case uiTest = "UI Test"
}
TestType.getRawValues()
The advantage here, is that I can add a new case for integrationTest and I only need to add that in one place.
You could just add a static property to return all enum values. For example:
enum RelationshipStatus: String {
case Single = "Single"
case Married = "Married"
case ItsComplicated = "It's complicated"
static let all: [RelationshipStatus] = [.Single, .Married, .ItsComplicated]
}
for status in RelationshipStatus.all {
print(status.rawValue)
}