swift How to use enum as parameter in constructing struct? - struct

I was doing an experiment of Swift programming book and stuck with construct a struct inner the struct itself. But the error reported the parameter is unwrapped. How could I take it value as parameter?
struct Card {
var rank: Rank
var suit: Suit
func simpleDescription() -> String {
return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
}
func FullDeck() -> Card[] {
var deck: Card[]
for i in 1...13
{
for j in 0...3
{
let rank_para = Rank.fromRaw(i)
let suit_para = Suit.fromRaw(j)
**deck.append(Card(rank: rank_para, suit : suit_para ))
//value of optional type unwrapped;did you mean to use ? or !**
}
}
return deck
}
}
enum Rank: Int {
case Ace = 1
case Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten
case Jack, Queen, King
func simpleDescription() -> String {
switch self {
case .Ace:
return "ace"
case .Jack:
return "jack"
case .Queen:
return "queen"
case .King:
return "king"
default:
return String(self.toRaw())
}
}
func compare(sec:Rank) -> Bool {
var first = 0
var second = 0
if self.toRaw() == 1 {
first = 1
} else {
first = self.toRaw()
}
if sec.toRaw() == 1 {
second = 1
} else {
second = self.toRaw()
}
return first > second
}
}
enum Suit: Int{
case Spades = 0
case Hearts, Diamonds, Clubs
func simpleDescription() -> String {
switch self {
case .Spades:
return "spades"
case .Hearts:
return "hearts"
case .Diamonds:
return "diamonds"
case .Clubs:
return "clubs"
}
}
}

the fromRaw method returns an optional value: Rank? and Suit?. That means that the value could be nil. You need to check for that:
if let aRank = rank_para {
if let aSuit = suit_para {
deck.append(Card(rank: aRank, suit: aSuit))
}
}
By using "if let", you "unwrap" the optional value into a value (aRank and aSuit) that is no longer optional (cannot be nil).
Another way to do that:
if rank_para and suit_para {
deck.append(Card(rank: rank_para!, suit: suit_para!))
}
Here, you are checking if rank_para and suit_para are nil. If they both are not, you call append and "unwrap" the optional values using !. ! means if the value is nil throw a runtime error, otherwise, treat this variable as if it cannot be nil.

Related

How to get argument of a variant?

Having these enums
pub enum Symbol {
X,
O,
}
pub enum CellContent {
Move(Symbol),
Empty,
}
and
let cell_content = CellContent::Move(Symbol::X);
how can I get the Symbol ? Of course if it's of variant Move(Symbol)
This doesn't work
if cell_0_0 == Move(a_symbol) {
return Some(a_symbol);
}
I cannot also do the following because I must do nothing (code must continue evaluation); and the following doesn't even compile at all !
match cell_0_0 {
Move(symbol) => return symbol;
_ => // do nothing;
}
// code must go on to check further conditions
| I still have some problem with basic syntax of rust, so I'm experiencing making some basic programs
So question is
How to
check if my variable is a variant of Move(Symbol)
if yes return (a copy of) symbol
else do nothing, so code can go on and do more checks
?
Edit 1: Full (not working) code
pub fn some_one_win(&self) -> Option<Symbol> {
let cell_0_0: CellContent = self.table[0][0];
let cell_0_1: CellContent = self.table[0][1];
let cell_0_2: CellContent = self.table[0][2];
if cell_0_0 == cell_0_1 && cell_0_0 == cell_0_2 {
match cell_0_0 {
Move(symbol) => return symbol;
_ => // how to 'do nothing' here ?;
}
}
let cell_1_0: CellContent = self.table[1][0];
let cell_1_1: CellContent = self.table[1][1];
let cell_1_2: CellContent = self.table[1][2];
if cell_1_0 == cell_1_1 && cell_1_0 == cell_1_2 {
match cell_1_0 {
Move(symbol) => return symbol;
_ => // how to 'do nothing' here ?;
}
}
... and so on ..
}
I cannot also do the following because I must do nothing (code must continue evaluation); and the following doesn't even compile at all !
match cell_0_0 {
Move(symbol) => return symbol;
_ => // do nothing;
}
You can do that, if you get the syntax right:
match cell_0_0 {
CellContent::Move(symbol) => {
return Some(symbol);
}
_ => {} // do nothing
}
But as PitaJ mentioned, when there’s only one pattern plus _, the if let construct is usually a cleaner alternative:
if let CellContent::Move(symbol) = cell_0_0 {
return Some(symbol);
}

Access a struct property by its name as a string in Swift

Let's say I have the following struct in Swift:
struct Data {
let old: Double
let new: Double
}
Now I have a class with an array of Data structs:
class MyClass {
var myDataArray: [Data]
}
Now let's say I want to calculate the average of either the old or the new values:
func calculateAverage(oldOrNew: String) -> Double {
var total = 0.0
count = 0
for data in myDataArray {
total += data.oldOrNew
count++
}
return total / Double(count)
}
And then:
let oldAverage = calculateAverage("old")
let newAverage = calculateAverage("new")
But this obviously doesn't work, since oldOrNew is not a member of my struct.
How can I access old or new from "old" or "new" ?
What about this "reflection-less" solution?
struct Data {
let old: Double
let new: Double
func valueByPropertyName(name:String) -> Double {
switch name {
case "old": return old
case "new": return new
default: fatalError("Wrong property name")
}
}
}
Now you can do this
let data = Data(old: 0, new: 1)
data.valueByPropertyName("old") // 0
data.valueByPropertyName("new") // 1
You're looking for key-value-coding (KVC) that is accessing properties by key (path).
Short answer: A struct does not support KVC.
If the struct is not mandatory in your design use a subclass of NSObject there you get KVC and even operators like #avg for free.
class MyData : NSObject {
#objc let old, new: Double
init(old:Double, new:Double) {
self.old = old
self.new = new
}
}
let myDataArray : NSArray = [MyData(old: 1, new: 3), MyData(old:5, new: 9), MyData(old: 12, new: 66)]
let averageOld = myDataArray.value(forKeyPath:"#avg.old")
let averageNew = myDataArray.value(forKeyPath: "#avg.new")
Edit: In Swift 4 a struct does support Swift KVC but the operator #avg is not available
You wouldn't access a struct property by name in Swift any more than you would in C++. You'd provide a block.
Extemporaneous:
func calculateAverage(getter: (Data) -> Double) {
... total += getter(data) ...
}
...
calculateAverage({$0.old})
calculateAverage({$0.new})
Possibly with average {$0.old} being a more natural syntax — the verb isn't really helpful and if you're asserting what it is, not what the computer should do, then omitting the brackets looks fine.

How to check if a string contains an int? -Swift

I need to know if a string contains an Int to be sure that a name the user entered is a valid full name,
for that I need to either make the user type only chars, or valid that there are no ints in the string the user entered.
Thanks for all the help.
You can use Foundation methods with Swift strings, and that's what you should do here. NSString has built in methods that use NSCharacterSet to check if certain types of characters are present. This translates nicely to Swift:
var str = "Hello, playground1"
let decimalCharacters = CharacterSet.decimalDigits
let decimalRange = str.rangeOfCharacter(from: decimalCharacters)
if decimalRange != nil {
print("Numbers found")
}
If you're interested in restricting what can be typed, you should implement UITextFieldDelegate and the method textField(_:shouldChangeCharactersIn:replacementString:) to prevent people from typing those characters in the first place.
Simple Swift 4 version using rangeOfCharacter method from String class:
let numbersRange = stringValue.rangeOfCharacter(from: .decimalDigits)
let hasNumbers = (numbersRange != nil)
This method is what i use now for checking if a string contains a number
func doStringContainsNumber( _string : String) -> Bool{
let numberRegEx = ".*[0-9]+.*"
let testCase = NSPredicate(format:"SELF MATCHES %#", numberRegEx)
let containsNumber = testCase.evaluateWithObject(_string)
return containsNumber
}
If your string Contains a number it will return true else false. Hope it helps
//Swift 3.0 to check if String contains numbers (decimal digits):
let someString = "string 1"
let numberCharacters = NSCharacterSet.decimalDigits
if someString.rangeOfCharacter(from: numberCharacters) != nil
{ print("String contains numbers")}
else if someString.rangeOfCharacter(from: numberCharacters) == nil
{ print("String doesn't contains numbers")}
//A function that checks if a string has any numbers
func stringHasNumber(_ string:String) -> Bool {
for character in string{
if character.isNumber{
return true
}
}
return false
}
/// Check stringHasNumber function
stringHasNumber("mhhhldiddld")
stringHasNumber("kjkdjd99900")
if (ContainsNumbers(str).count > 0)
{
// Your string contains at least one number 0-9
}
func ContainsNumbers(s: String) -> [Character]
{
return s.characters.filter { ("0"..."9").contains($0)}
}
Swift 2.3. version working.
extension String
{
func containsNumbers() -> Bool
{
let numberRegEx = ".*[0-9]+.*"
let testCase = NSPredicate(format:"SELF MATCHES %#", numberRegEx)
return testCase.evaluateWithObject(self)
}
}
Usage:
//guard let firstname = textField.text else { return }
let testStr1 = "lalalala"
let testStr2 = "1lalalala"
let testStr3 = "lal2lsd2l"
print("Test 1 = \(testStr1.containsNumbers())\nTest 2 = \(testStr2.containsNumbers())\nTest 3 = \(testStr3.containsNumbers())\n")
You need to trick Swift into using Regex by wrapping up its nsRegularExpression
class Regex {
let internalExpression: NSRegularExpression
let pattern: String
init(_ pattern: String) {
self.pattern = pattern
var error: NSError?
self.internalExpression = NSRegularExpression(pattern: pattern, options: .CaseInsensitive, error: &error)
}
func test(input: String) -> Bool {
let matches = self.internalExpression.matchesInString(input, options: nil, range:NSMakeRange(0, countElements(input)))
return matches.count > 0
}
}
if Regex("\\d/").test("John 2 Smith") {
println("has a number in the name")
}
I got these from http://benscheirman.com/2014/06/regex-in-swift/
let numericCharSet = CharacterSet.init(charactersIn: "1234567890")
let newCharSet = CharacterSet.init(charactersIn: "~`##$%^&*(){}[]<>?")
let sentence = "Tes#ting4 #Charact2er1Seqt"
if sentence.rangeOfCharacter(from: numericCharSet) != nil {
print("Yes It,Have a Numeric")
let removedSpl = sentence.components(separatedBy: newCharSet).joined()
print(sentence.components(separatedBy: newCharSet).joined())
print(removedSpl.components(separatedBy: numericCharSet).joined())
}
else {
print("No")
}

Swift Form Validation - Check if Int or String has been entered

I am trying to validate a form to make sure the user has entered an integer number and not a string. I can check if the number is an integer as follows:
var possibleNumber = timeRetrieved.text
convertedNumber = possibleNumber.toInt()
// convertedNumber is inferred to be of type "Int?", or "optional Int"
if convertedNumber != nil {
println("It's a number!")
totalTime = convertedNumber!
}
My problem is I want to make sure the user has not entered any text, doubles etc. I only want integer numbers. The following code does not work because it evaluates true if the variable is an integer. What code should I use to evaluate if variable is not an integer?
if convertedNumber != nil {
let alertController = UIAlertController(title: "Validation Error", message: "You must enter an integer number!", preferredStyle: .Alert)
let alertAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.Destructive, handler: {(alert : UIAlertAction!) in
alertController.dismissViewControllerAnimated(true, completion: nil)
})
alertController.addAction(alertAction)
presentViewController(alertController, animated: true, completion: nil)
Swift 2 changes this: as both Int("abc") and Int("0") return 0, integer conversion can't be used. You could use this:
class Validation {
static func isStringNumerical(string : String) -> Bool {
// Only allow numbers. Look for anything not a number.
let range = string.rangeOfCharacterFromSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet)
return (range == nil)
}
}
It uses a decimalDigitCharacterSet, and can be changed to use whatever character set you want.
func testIsStringNumerical() {
XCTAssertEqual(SignUpLoyaltyViewController.isStringNumerical("123"), true)
XCTAssertEqual(SignUpLoyaltyViewController.isStringNumerical(""), true)
XCTAssertEqual(SignUpLoyaltyViewController.isStringNumerical("12AA"), false)
XCTAssertEqual(SignUpLoyaltyViewController.isStringNumerical("123.4"), false)
}
This is dramatically faster than the Regex answer. (2000 runs, 0.004s vs regex 0.233s)
If the number the user has entered is not an integer, convertedNumber will be nil. Just add an else clause in which you can show the alert.
Int initializer
This works in Swift 2.2 and above. It is based on Minhal Khan's answer which illustrates that Int has an initializer with this signature: init?(_ text: String, radix: Int = default). Since radix has a default value, it can be left out. *more info on this initializer is found here.
var totalTime: Int?
let possibleInt = timeRetrieved.text ?? ""
if let convertedNumber = Int(possibleInt) {
print("'\(possibleInt)' is an Int")
totalTime = convertedNumber
}
else {
print("'\(possibleInt)' is not an Int")
}
print("totalTime: '\(totalTime)'")
Note: I assumed timeRetrieved is a UITextField. The UITextField text property is an optional string (though programmatically not allowed to be nil). Therefore, the compiler requires it be unwrapped. I used the nil coalescing operator (??) to substitute a nil for empty string which does not yield an integer as desired. Here's a post that discusses the optionality of UITextfield.text.
What i had done was get the value and check if it could convert it, works for me
var enteredText = Int(textfield.text)
if enteredText == nil{
//String entered
}
else{
//Int entered
}
Based on #Graham Perks answer a Swift 3 Version as string extension:
extension String
{
var isNumeric: Bool
{
let range = self.rangeOfCharacter(from: CharacterSet.decimalDigits.inverted)
return (range == nil)
}
}
Usage:
"123".isNumeric // true
"abc".isNumeric // false
I really recommend using a REGEX, I was recently trying to validate 10 digit phone numbers using if let _ = Int(stringToTest)... and on 32 bit hardware, I faced range issues.
func validate(value: String) -> Bool {
let PHONE_REGEX = "\\d{10}"
let phoneTest = NSPredicate(format: "SELF MATCHES %#", PHONE_REGEX)
let result = phoneTest.evaluateWithObject(value)
if result == true {
log.info("'\(self.text!)' is a valid number.")
} else {
log.info("'\(self.text!)' is an invalid number.")
}
return result
}

How to find out element position in slice?

How does one determine the position of an element present in slice?
I need something like the following:
type intSlice []int
func (slice intSlice) pos(value int) int {
for p, v := range slice {
if (v == value) {
return p
}
}
return -1
}
Sorry, there's no generic library function to do this. Go doesn't have a straight forward way of writing a function that can operate on any slice.
Your function works, although it would be a little better if you wrote it using range.
If you happen to have a byte slice, there is bytes.IndexByte.
You can create generic function in idiomatic go way:
func SliceIndex(limit int, predicate func(i int) bool) int {
for i := 0; i < limit; i++ {
if predicate(i) {
return i
}
}
return -1
}
And usage:
xs := []int{2, 4, 6, 8}
ys := []string{"C", "B", "K", "A"}
fmt.Println(
SliceIndex(len(xs), func(i int) bool { return xs[i] == 5 }),
SliceIndex(len(xs), func(i int) bool { return xs[i] == 6 }),
SliceIndex(len(ys), func(i int) bool { return ys[i] == "Z" }),
SliceIndex(len(ys), func(i int) bool { return ys[i] == "A" }))
You could write a function;
func indexOf(element string, data []string) (int) {
for k, v := range data {
if element == v {
return k
}
}
return -1 //not found.
}
This returns the index of a character/string if it matches the element. If its not found, returns a -1.
There is no library function for that. You have to code by your own.
Go supports generics as of version 1.18, which allows you to create a function like yours as follows:
func IndexOf[T comparable](collection []T, el T) int {
for i, x := range collection {
if x == el {
return i
}
}
return -1
}
If you want to be able to call IndexOf on your collection you can alternatively use #mh-cbon's technique from the comments.
You can just iterate of the slice and check if an element matches with your element of choice.
func index(slice []string, item string) int {
for i := range slice {
if slice[i] == item {
return i
}
}
return -1
}
Since Go 1.18 you can also use the experimental generic slices package from https://pkg.go.dev/golang.org/x/exp/slices like this:
package main
import "golang.org/x/exp/slices"
func main() {
s := []int{1,2,3,4,5}
wanted := 3
idx := slices.Index(s, wanted)
fmt.Printf("the index of %v is %v", wanted, idx)
}
It will return -1, if wanted is not in the slice. Test it at the playground.
This is my preferred way, since this might become part of the standard library someday.
Another option is to sort the slice using the sort package, then search for the thing you are looking for:
package main
import (
"sort"
"log"
)
var ints = [...]int{74, 59, 238, -784, 9845, 959, 905, 0, 0, 42, 7586, -5467984, 7586}
func main() {
data := ints
a := sort.IntSlice(data[0:])
sort.Sort(a)
pos := sort.SearchInts(a, -784)
log.Println("Sorted: ", a)
log.Println("Found at index ", pos)
}
prints
2009/11/10 23:00:00 Sorted: [-5467984 -784 0 0 42 59 74 238 905 959 7586 7586 9845]
2009/11/10 23:00:00 Found at index 1
This works for the basic types and you can always implement the sort interface for your own type if you need to work on a slice of other things. See http://golang.org/pkg/sort
Depends on what you are doing though.
I had the same issue few months ago and I solved in two ways:
First method:
func Find(slice interface{}, f func(value interface{}) bool) int {
s := reflect.ValueOf(slice)
if s.Kind() == reflect.Slice {
for index := 0; index < s.Len(); index++ {
if f(s.Index(index).Interface()) {
return index
}
}
}
return -1
}
Use example:
type UserInfo struct {
UserId int
}
func main() {
var (
destinationList []UserInfo
userId int = 123
)
destinationList = append(destinationList, UserInfo {
UserId : 23,
})
destinationList = append(destinationList, UserInfo {
UserId : 12,
})
idx := Find(destinationList, func(value interface{}) bool {
return value.(UserInfo).UserId == userId
})
if idx < 0 {
fmt.Println("not found")
} else {
fmt.Println(idx)
}
}
Second method with less computational cost:
func Search(length int, f func(index int) bool) int {
for index := 0; index < length; index++ {
if f(index) {
return index
}
}
return -1
}
Use example:
type UserInfo struct {
UserId int
}
func main() {
var (
destinationList []UserInfo
userId int = 123
)
destinationList = append(destinationList, UserInfo {
UserId : 23,
})
destinationList = append(destinationList, UserInfo {
UserId : 123,
})
idx := Search(len(destinationList), func(index int) bool {
return destinationList[index].UserId == userId
})
if idx < 0 {
fmt.Println("not found")
} else {
fmt.Println(idx)
}
}
Another option if your slice is sorted is to use SearchInts(a []int, x int) int which returns the element index if it's found or the index the element should be inserted at in case it is not present.
s := []int{3,2,1}
sort.Ints(s)
fmt.Println(sort.SearchInts(s, 1)) // 0
fmt.Println(sort.SearchInts(s, 4)) // 3
https://play.golang.org/p/OZhX_ymXstF

Resources