I have a Player class that stores a rating property of type Int:
class Player {
typealias Rating: Int
var rating: Rating = 0
}
I then have various Range instances that specify the level that a given player is at:
private let level1Range = 0 ..< 100
private let level2Range = 100 ..< 500
I can then switch on the player rating property to obtain the level that the player is at:
switch rating {
case level1Range:
print("On Level 1")
case level2Range:
print("On Level 2")
default:
break
}
I want to be able to say what the next level is and how far away the player is from that next level.
I'm not sure of the best way to go out this problem. I started by making an array:
private var ratingRanges: [Range] {
return [level1Range, level2Range]
}
But I get the error:
Reference to generic type 'Range' requires arguments in <...>
Insert '<<#Bound: Comparable#>>'
If this worked, I guess I could then find the first non-zero value:
ratingRanges.first(where: { $0.min() - self.rating > 0 })
in order to locate the next range.
Or is there a more efficient method to achieve this?
Thanks for any help
You need to provide the generic placeholder type of the Range:
private var ratingRanges: [Range<Rating>] {
return [level1Range, level2Range]
}
Or simpler, with automatic type inference, as a (lazy) stored property:
private lazy var ratingRanges = [level1Range, level2Range]
Determining the next range can then be done as
func nextRange() -> Range<Rating>? {
return ratingRanges.first(where: { $0.lowerBound > rating})
}
My solution is to create Level enum:
enum Level: Int {
case level1 = 1
case level2
init?(rating: Int) {
switch rating {
case Level.level1.range:
self = .level1
case Level.level2.range:
self = .level2
default:
return nil
}
}
var range: CountableRange<Int> {
switch self {
case .level1:
return level1Range
case .level2:
return level2Range
}
}
}
And then all you need to do is to add following methods to your Player class:
func nextLevel() -> Level? {
guard let currentLevel = Level(rating: rating) else {
return nil
}
guard let nextLevel = Level(rawValue: currentLevel.rawValue + 1) else {
return nil
}
return nextLevel
}
func distanceTo(level: Level) -> Int {
let levelLowerBound = level.range.lowerBound
return levelLowerBound - rating
}
May you should to keep the maximum value of range only. For example, instead of
private let level1Range = 0 ..< 100
private let level2Range = 100 ..< 500
you can use
private let level1MaxRating = 100
private let level2MaxRating = 500
and compare with
switch rating {
case 0...level1MaxRating:
print("level 1")
case (level1MaxRating+1)...level2MaxRating:
print("level 2")
}
Related
I have this enum (with associated type)
enum CookieType {
case regular(type: Int)
case gem(type: GemType)
}
struct Cookie {
let type: CookieType
let otherStuff...
}
Now if i want to do pattern matching, i can do this with no problem:
if case .gem == cookie.type {
}
However, I want to use case .gem == cookie.type as a boolean. The following gives error
var cookies: [Cookie] {
return [cookieA, cookieB]
}
var gems: [Cookie] {
return cookies.filter { case $0.type == .gem } // this has error
}
which means case $0.type == .gem is not a boolean. How can I deal with this?
case .gem = cookie.type is not a boolean expression, and if statements not only accept boolean expressions. However, in the closure argument for filter, you must write an Bool expression or a block that returns Bool.
One way to do that is:
cookies.filter {
if case $0.type = .gem { return true }
else { return false }
}
Or, you can add convenient properties to CookieType that gives you Bool values, if you tend to do this a lot:
enum CookieType {
case regular(type: Int)
case gem(type: GemType)
var isRegular: Bool {
if case .regular = self { return true }
else { return false }
}
var isGem: Bool {
if case .gem = self { return true }
else { return false }
}
}
Then you can do:
cookies.filter(\.isGem)
Normally, if case and switch statements are abstractions based on the pattern matching operator ~=, but that's not how enums with associated types are implemented. As such, for the time being, you need to reverse-engineer how it might have been done, to allow this:
cookies.filter { CookieType.regular ~= $0.type }
I do not believe it is possible to avoid the explicit CookieType there, but it still reads better than all alternatives.
/// Match `enum` cases with associated values, while disregarding the values themselves.
/// - Parameter case: Looks like `Enum.case`.
public func ~= <Enum, AssociatedValue>(
case: (AssociatedValue) -> Enum,
instance: Enum
) -> Bool {
Mirror.associatedValue(of: instance, ifCase: `case`) != nil
}
public extension Mirror {
/// Get an `enum` case's `associatedValue`.
static func associatedValue<AssociatedValue>(
of subject: Any,
_: AssociatedValue.Type = AssociatedValue.self
) -> AssociatedValue? {
guard let childValue = Self(reflecting: subject).children.first?.value
else { return nil }
if let associatedValue = childValue as? AssociatedValue {
return associatedValue
}
let labeledAssociatedValue = Self(reflecting: childValue).children.first
return labeledAssociatedValue?.value as? AssociatedValue
}
/// Get an `enum` case's `associatedValue`.
/// - Parameter case: Looks like `Enum.case`.
static func associatedValue<Enum, AssociatedValue>(
of instance: Enum,
ifCase case: (AssociatedValue) throws -> Enum
) rethrows -> AssociatedValue? {
try associatedValue(of: instance).filter {
.equate(try `case`($0), to: instance) {
Self(reflecting: $0).children.first?.label
}
}
}
}
public extension Optional {
/// Transform `.some` into `.none`, if a condition fails.
/// - Parameters:
/// - isSome: The condition that will result in `nil`, when evaluated to `false`.
func filter(_ isSome: (Wrapped) throws -> Bool) rethrows -> Self {
try flatMap { try isSome($0) ? $0 : nil }
}
}
public extension Equatable {
/// Equate two values using a closure.
static func equate<Wrapped, Equatable: Swift.Equatable>(
_ optional0: Wrapped?, to optional1: Wrapped?,
using transform: (Wrapped) throws -> Equatable
) rethrows -> Bool {
try optional0.map(transform) == optional1.map(transform)
}
}
Inspired by #Jessy and SwiftLee, here is my solution:
// -----------------------
// CaseReflectable
// -----------------------
// designed for enums only
// (use it on other types not recommended)
protocol CaseReflectable {}
// default behaviors.
extension CaseReflectable {
/// case name
var caseName: String {
let mirror = Mirror(reflecting: self)
// enum cases:
// - normal case: no children
// - case with associated values: one child (with label)
guard let label = mirror.children.first?.label else {
return "\(self)" // normal case
}
// case with associated values
return label
}
/// associated values
var associatedValues: Any? {
// if no children, a normal case, no associated values.
guard let firstChild = Mirror(reflecting: self).children.first else {
return nil
}
return firstChild.value
}
}
// --------------------------
// custom operator ~=
// --------------------------
/// match enum cases with associated values, while disregarding the values themselves.
/// usage: `Enum.enumCase ~= instance`
func ~= <Enum: CaseReflectable, AssociatedValue>(
// an enum case (with associated values)
enumCase: (AssociatedValue) -> Enum, // enum case as function
// an instance of Enum
instance: Enum
) -> Bool
{
// if no associated values, `instance` can't be of `enumCase`
guard let values = instance.associatedValues else { return false }
// if associated values not of the same type, return false
guard values is AssociatedValue else { return false }
// create an instance from `enumCase` (as function)
let case2 = enumCase(values as! AssociatedValue)
// if same case name, return true
return case2.caseName == instance.caseName
}
// ------------
// Enum
// ------------
// enum with associated values
// (conforms to `CaseReflectable`)
enum Enum: CaseReflectable {
case int(Int)
case int2(Int)
case person(name: String, age: Int)
case str(String)
}
// ------------
// main
// ------------
let a: Enum = .int(3)
Enum.int ~= a // true
Enum.int2 ~= a // false
let joe = Enum.person(name: "joe", age: 8)
Enum.person ~= joe // true
Enum.int ~= joe // false
// array of enum cases
let items: [Enum] = [
.int(1), .str("hi"), .int(2)
]
// filter enum cases
let filtered = items.filter { Enum.int ~= $0 }
print(filtered) // [Enum.int(1), Enum.int(2)]
I am making a rhythm app, but I can't seem to randomize the circles. Here is my code:
var alternator = 0
var fallTimer:NSTimer?
var flag:Bool = true
let circleIndexes = (0..<5).map { return NSNumber(integer: $0) }
let randomIndexes = GKRandomSource.sharedRandom().arrayByShufflingObjectsInArray(circleIndexes) as! [Int]
func fallCircleWrapper() {
if (flag == true) {
self.alternator += 1
} else {
self.alternator -= 1
}
if (self.alternator == 0) {
flag = true
} else if (self.alternator == 5) {
flag = false
}
self.hitAreaArray[randomIndexes[self.alternator]].emitNote(self.texture!)
}
The problem is with this line:
let randomIndexes = GKRandomSource.sharedRandom().arrayByShufflingObjectsInArray(circleIndexes) as! [Int]
which gives the error "Instance member 'circleIndexes' cannot be used on type 'GameScene'". How should I go about fixing this?
Consider this example that reproduces your error on a simpler scale:
func agePlusOne(_ num: Int) -> Int { return num + 1 }
class Cat {
var age = 5
var addedAge = agePlusOne(age) // ERROR!
}
You're trying to use a property /before/ it has been initialized... that is, before init() has finished, thus giving you a self to work with.
A way around this is to use lazy properties... lazy properties are initialized only when first called by an object.. that is, they are properly initialized only after there is a self available.
private(set) lazy var randomIndices: [Int] = {
GKRandomSource.sharedRandom().arrayByShufflingObjectsInArray(self.circleIndexes) as! [Int]
}()
computed and static properties are also lazy by default, without needing to use the keyword. Unfortunately, you cannot use lazy let, so the private(set) was added to give you more immutability, but not total.
PS, this is Swift3 so you may need to convert it to Swift2.
I am trying to build a simple Library-Shelf-Book model in Swift and I am running into a strange issue. Within the following Library Class, my func total book count is returning 0 every time. I know its probably something simple I'm overlooking but any help would be awesome. Here is my library class:
class Library
{
var allShelves:[Shelf]=[]
var allBooksCount = 0
var description: String{
return "This Library has \(allShelves.count) Shelves with \(allBooksCount) books"
}
func addNewShelf(newShelf: Shelf){
var newShelf = Shelf()
self.allShelves.append(newShelf)
println("There are \(allShelves.count) SHELVES in this library")
}
func totalBookCount() -> Int{
for currentShelf in allShelves{
allBooksCount = currentShelf.numberOfBooks
}
return allBooksCount
}
}
Here is my Shelf class:
class Shelf
{
var allBooksOnShelf:[Book] = []
var numberOfBooks = 0
init(){
self.allBooksOnShelf = []
}
var description: String{
return "This Shelf has \(allBooksOnShelf.count) Books"
}
func addNewBook(newBookToAddToShelf: Book){
let newBook = Book(bookName: newBookToAddToShelf.bookName)
self.allBooksOnShelf += [newBookToAddToShelf]
numberOfBooks = allBooksOnShelf.count
println("new book called \(newBook.bookName)")
}
}
Here are my tests:
let newLibrary = Library()
//println(newLibrary.description)
let libraryShelf1 = Shelf()
newLibrary.addNewShelf(libraryShelf1)
let libraryShelf2 = Shelf()
newLibrary.addNewShelf(libraryShelf2)
libraryShelf1.addNewBook(Book(bookName: "Game Of Thrones"))
libraryShelf1.addNewBook(Book(bookName: "Hunger Games"))
libraryShelf1.addNewBook(Book(bookName: "Return of the Jedi"))
println("this Shelf has \(libraryShelf1.allBooksOnShelf.count) books")
newLibrary.totalBookCount()
println(newLibrary.description)
newLibrary.totalBookCount() always returns 0.
I see 2 errors:
Error 1
func addNewShelf(newShelf: Shelf){
var newShelf = Shelf()
^^^^^^^^^^^^
self.allShelves.append(newShelf)
println("There are \(allShelves.count) SHELVES in this library")
}
Here you are redefining newShelf parameter as a local variable, losing what is passed in. The correct implementation is:
func addNewShelf(newShelf: Shelf){
self.allShelves.append(newShelf)
println("There are \(allShelves.count) SHELVES in this library")
}
Error 2
Here:
func totalBookCount() -> Int{
for currentShelf in allShelves{
allBooksCount = currentShelf.numberOfBooks
}
return allBooksCount
}
at each iteration you reinitialize the allBooksCount property, so in the end what the func returns is the count for the last shelf in the loop. You should add instead (using the += operator) - moreover resetting the counter at beginning would also be a good idea (otherwise if you call the function multiple times you have incorrect results):
func totalBookCount() -> Int{
self.allBooksCount = 0
for currentShelf in allShelves{
allBooksCount += currentShelf.numberOfBooks
}
return allBooksCount
}
HI related to an earlier question which I kind of made a mess of so re posting. What I need to do is return nil if a certain type of blog post is passed into my function
this is the class instance that I am passing in
class BlogPost {
var type = Int(arc4random_uniform(UInt32(types.count)))
var views : Int?
var author : String
var email : String
var order : Int = 0
init(author : String, email : String, order : Int) {
self.author = author
self.email = email
self.order = order
}
func teaser() -> (authorName : String, numberOfViews : Int) {
author = ""
views = 0
if views == 0 {
println("no one has read your blog yet")
} else {
println("\(author) has \(views)")
}
return(author, views!)
}
}
and this is the function which I am having a problem with
func randomViews(blog : BlogPost?) {
var vids = types[2]
if blog?.type == vids {
return nil
} else {
let viewCount : [Int] = [10, 20, 30, 40, 50]
var unsignedArrayCount = UInt32(viewCount.count)
var unsignedRandom = arc4random_uniform(unsignedArrayCount)
var random = unsignedRandom
switch (random) {
case 10:
println("Nil")
case 10, 20 :
println("0")
default:
random
}
}
}
the error I receive is
"$T4?? does not conform to protocol '_RawOptionSetType'"
hope this is clearer than before and hope you can help.
Many thanks
no probs here is my types array
let types : [String] = ["technology", "memos", "animals"]
I have no also dropped the Int and updated the type within blog class
var type = (arc4random_uniform(UInt32(types.count)))
basically if "animals" is passed in as a blog type I need to return nil otherwise do nothing and just carry on. So just need to know what return type would be best to use? thanks
cheers Rob ok this is what I did I just created a seperate function to access a random element of the array
func randType() -> String {
var unsignedArrayCount = UInt32(types.count)
var unsignedrandom = arc4random_uniform(unsignedArrayCount)
var random = Int(unsignedrandom)
return types[random]
}
then as you said no need for a return type just return
func randomViews(blog : BlogPost?) {
var vids = types[2]
if blog?.type == vids {
//println("NIL")
return
} else {
let viewCount : [Int] = [10, 20, 30, 40, 50]
var unsignedArrayCount = UInt32(viewCount.count)
var unsignedRandom = arc4random_uniform(unsignedArrayCount)
var random = unsignedRandom
switch (random) {
case 10:
println("Nil")
case 10, 20 :
println("0")
default:
random
}
}
}
this seems to work so I ll go with for now. Sorry for the dragged out post but at least I hung in there , didnt quit and got it going (somewhat) thanks
Your randomViews is not specifying the return type:
func randomViews(blog : BlogPost?) -> UInt32? {
I presume it's a UInt32? because that is what I see in the else branch.
Moreover, the else branch is missing a return statement, which I presume should be random. The fixed function should look like:
func randomViews(blog : BlogPost?) -> UInt32? {
var vids = types[2]
if blog?.type == vids {
return nil
} else {
let viewCount : [Int] = [10, 20, 30, 40, 50]
var unsignedArrayCount = UInt32(viewCount.count)
var unsignedRandom = arc4random_uniform(unsignedArrayCount)
var random = unsignedRandom
switch (random) {
case 10:
println("Nil")
case 10, 20 :
println("0")
default:
random
}
return random
}
}
A couple of observations:
You've defined BlogPost.type to be an Int. But I presume that types is still defined as [String]. You need to make sure these two match.
For example, now that you've defined type to be Int, you might want if blog?.type == 2 rather than if blog?.type == types[2]. You don't want to compare your numeric type to a string, but rather to the number 2.
You haven't specified any return type in your definition of randomViews, but you try to do return nil. What do you want randomViews to return (if anything)? Make sure your function's return type matches what you supply in the return statements.
I have an object FormField which has two properties: a String name, and a value which can accept any type--hence I've made it Any!. However, I've been told in a separate question to use an enum with associated values instead of Any!.
enum Value {
case Text(String!)
case CoreDataObject(NSManagedObject!)
}
class FormField {
var name: String
var value: Value?
// initializers...
}
This approach makes it awfully verbose to check for nullity however. If I wanted to display an alert view for all the missing fields in the form, I'll have to repeat a nil check for every case in a switch statement:
for field in self.fields {
if let value = field.value {
switch value {
case .Text(let text):
if text == nil {
missingFields.append(field.name)
}
case .CoreDataObject(let object):
if object == nil {
missingFields.append(field.name)
}
}
}
}
Is there a shorter way of accessing the enum's associated value, regardless of the type? If I make FormField.value an Any! the above code would be as easy as:
for field in self.fields {
if field.value == nil {
missingFields.append(field.name)
}
}
Define a method isMissing() inside the enum - write it once and only once. Then you get nearly exactly what you prefer:
for field in self.fields {
if field.value.isMissing() {
missingFields.append(field.name)
}
}
It would look something like this (from the Swift Interpreter):
1> class Foo {}
>
2> enum Value {
3. case One(Foo!)
4. case Two(Foo!)
5.
6. func isMissing () -> Bool {
7. switch self {
8. case let .One(foo): return foo == nil
9. case let .Two(foo): return foo == nil
10. }
11. }
12. }
13> let aVal = Value.One(nil)
aVal: Value = One {
One = nil
}
14> aVal.isMissing()
$R0: Bool = true
With Swift 2 it's possible to get the associated value using reflection.
To make that easier just add the code below to your project and extend your enum with the EVAssociated protocol.
public protocol EVAssociated {
}
public extension EVAssociated {
public var associated: (label:String, value: Any?) {
get {
let mirror = Mirror(reflecting: self)
if let associated = mirror.children.first {
return (associated.label!, associated.value)
}
print("WARNING: Enum option of \(self) does not have an associated value")
return ("\(self)", nil)
}
}
}
Then you can access the .asociated value with code like this:
class EVReflectionTests: XCTestCase {
func testEnumAssociatedValues() {
let parameters:[EVAssociated] = [usersParameters.number(19),
usersParameters.authors_only(false)]
let y = WordPressRequestConvertible.MeLikes("XX", Dictionary(associated: parameters))
// Now just extract the label and associated values from this enum
let label = y.associated.label
let (token, param) = y.associated.value as! (String, [String:Any]?)
XCTAssertEqual("MeLikes", label, "The label of the enum should be MeLikes")
XCTAssertEqual("XX", token, "The token associated value of the enum should be XX")
XCTAssertEqual(19, param?["number"] as? Int, "The number param associated value of the enum should be 19")
XCTAssertEqual(false, param?["authors_only"] as? Bool, "The authors_only param associated value of the enum should be false")
print("\(label) = {token = \(token), params = \(param)")
}
}
// See http://github.com/evermeer/EVWordPressAPI for a full functional usage of associated values
enum WordPressRequestConvertible: EVAssociated {
case Users(String, Dictionary<String, Any>?)
case Suggest(String, Dictionary<String, Any>?)
case Me(String, Dictionary<String, Any>?)
case MeLikes(String, Dictionary<String, Any>?)
case Shortcodes(String, Dictionary<String, Any>?)
}
public enum usersParameters: EVAssociated {
case context(String)
case http_envelope(Bool)
case pretty(Bool)
case meta(String)
case fields(String)
case callback(String)
case number(Int)
case offset(Int)
case order(String)
case order_by(String)
case authors_only(Bool)
case type(String)
}
The code above is now available as a cocoapod susbspec at
https://github.com/evermeer/Stuff#enum
It also has an other nice enum extension for enumerating all enum values.
If the associated values were of the same type for all enum cases the following approach could help.
enum Value {
case text(NSString!), two(NSString!), three(NSString!) // This could be any other type including AnyClass
}
// Emulating "fields" datastruct for demo purposes (as if we had struct with properties).
typealias Field = (en: Value, fieldName: String)
let fields: [Field] = [(.text(nil),"f1"), (.two(nil), "f2"), (.three("Hey"), "f3")] // this is analog of "fields"
let arrayOfFieldNamesWithEmptyEnums: [String] = fields.compactMap({
switch $0.en {
case let .text(foo), let .two(foo), let .three(foo): if foo == nil { return $0.fieldName } else { return nil }}
})
print("arrayOfFieldNamesWithEmptyEnums \(arrayOfFieldNamesWithEmptyEnums)")
Many other things can be obtained similarly.
let arrayOfEnumsWithoutValues: [Value] = fields.compactMap({
switch $0.en {
case let .text(foo), let .two(foo), let .three(foo): if foo == nil { return $0.en } else { return nil }}
})
print("arrayOfEnumsWithoutValues \(arrayOfEnumsWithoutValues)")
// just to check ourselves
if let index = arrayOfEnumsWithoutValues.index(where: { if case .two = $0 { return true }; return false }) {
print(".two found at index \(index)")
}