new to programming and not quite grasping how this is wrong, any advice is appreciated.
func exercise() {
let alphabet = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"]
var char0 = alphabet.randomElement()
var char1 = alphabet.randomElement()
var char2 = alphabet.randomElement()
var char3 = alphabet.randomElement()
var char4 = alphabet.randomElement()
var char5 = alphabet.randomElement()
print(char0 + char1 + char2 + char3 + char4 + char5)
}
The important part of the error message to understand is that String and String? (optional) are considered to be 2 different types by the compiler.
For some cases the compiler can make a (implicit) conversion so the types are the same but not here since it can't convert nil and the + operator only works for two variables/literal values of the same type.
Consider this case
var char0 = alphabet.randomElement()
var char1 = alphabet.randomElement() ?? ""
If we now do print("0: \(char0) 1: \(char1)") the result is quite different for the 2 variables
0: Optional("p") 1: d
which we also see if we examin their types using print("0: \(type(of: char0)) 1: \(type(of: char1))")
0: Optional<String> 1: String
Others have explained why your code gives an error. (String.randomElement() returns an Optional<String>, which is not the same thing as a String, and you can't use + to concatenate Optionals.)
Another point: Your code is verbose and repetitive. There is a principle, "DRY", in computer science. It stands for "Don't Repeat Yourself". Any time where you have the same code over and over (maybe with slight variations) it is "code smell", and an opportunity to improve.
You could rewrite your code without repetition like this:
func exercise() {
let alphabet = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"]
var result = ""
for _ in 1...6 {
result.append(alphabet.randomElement() ?? "")
}
print(result)
}
In that version, the expression alphabet.randomElement() only appears once. You don't repeat yourself. It also uses the ?? "nil coalescing operator" to convert possibly nil results to blank strings.
Another way to handle it would be to define an override of the += operator that lets you append String optionals to a string:
public func +=(left: inout String, right: Optional<String>) {
left = left + (right ?? "")
}
Then your function becomes:
func exercise() {
let alphabet = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"]
var result = ""
for _ in 1...6 {
result += alphabet.randomElement()
}
print(result)
}
Yet another way would be to shuffle your source array, fetch the first 6 elements, and then join them back together into a String:
func exercise() {
let alphabet = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"]
let result = alphabet
.shuffled()[1...6]
.joined()
print(result)
}
Give default value to variables by using ?? operator in case of nil. It will change type of variables from String? to String
func exercise() {
let alphabet = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"]
var char0 = alphabet.randomElement() ?? ""
var char1 = alphabet.randomElement() ?? ""
var char2 = alphabet.randomElement() ?? ""
var char3 = alphabet.randomElement() ?? ""
var char4 = alphabet.randomElement() ?? ""
var char5 = alphabet.randomElement() ?? ""
print(char0 + char1 + char2 + char3 + char4 + char5)
}
I'm currently teaching myself Swift coming from a background of Python recently and Visual Basic originally. I would describe myself as competent in those codes but a novice in Swift. I'm trying to write a function that will return a set number of digits from either the start or end of a long integer. My chosen method has been to convert the integer to a string and then use the prefix or suffix command.
Whilst I can get the function to work if it has no flow control and uses either prefix or suffix (first lot of code), when I try to write one function that does both I get an unresolved identifier error on the turnStringToInteger variable (second lot of code). I'm pretty sure this is because the variable lives within the if {} but if I declare it outside of the if loop (hashed out) this also errors. I appreciate this will have a really simple answer but how do I use the return correctly with a nested if loop?
This works...
//Function to Trim Integer (Prefix Only)
func integerTrim(integer:Int, trimLength:Int) -> Int {
var strFromInteger = String(integer)
var trimmedString = strFromInteger.prefix(trimLength)
var intFromString = Int(trimmedString) ?? 0
return intFromString
}
//Declare Input
var inputInt = 12345678910
//Call Function
var result = integerTrim(integer: inputInt, trimLength: 4)
//Print Results
print(inputInt)
print(result)
This doesn't...!
//Function to trim integer prefix or suffix
func integerTrim(integer:Int, type:String, trimLength:Int) -> Int {
var typeID = type
//var turnStringToInteger: Int
if typeID == "P" {
var turnIntegerToString = String(integer)
var trimmedString = turnIntegerToString.prefix(trimLength)
var turnStringToIngeger = Int(trimmedString) ?? 0
}
else if typeID == "S" {
var turnIntegerToString = String(integer)
var trimmedString = turnIntegerToString.suffix(trimLength)
var turnStringToIngeger = Int(trimmedString) ?? 0
}
return turnStringToInteger
}
//Declare Input
var inputInt = 53737363856453
//Call Function
var result = integerTrim(integer: inputInt, type: "P", trimLength: 4)
//Print Results
print(inputInt)
print(result)
As I am self taught I appreciate I may also not be using best practices. I really want to learn to do this properly so if I am going about all of this the wrong way to begin with I would be equally happy to hear other approaches. For example I did consider turning the integer to an array and then creating the trimmed integer from positions within this array. Would this be more elegant?
If you want to access the variable outside of the scope where it is assigned, you need to declare it in the outer scope.
If you do that without assigning it an initial value, you get an error: variable 'turnStringToInteger' used before being initialized. That happens because Swift sees a path in which turnStringToInteger never gets assigned a value (imagine what happens if "X" is passed in for type).
So your real issue is the use of String as the type for type. It would be better to use an enum that expresses exactly what you want:
enum TrimType {
case prefix, suffix
}
func integerTrim(integer: Int, type: TrimType, trimLength: Int) -> Int {
let typeID = type
var turnStringToInteger: Int
switch typeID {
case .prefix:
let turnIntegerToString = String(integer)
let trimmedString = turnIntegerToString.prefix(trimLength)
turnStringToInteger = Int(trimmedString) ?? 0
case .suffix:
let turnIntegerToString = String(integer)
let trimmedString = turnIntegerToString.suffix(trimLength)
turnStringToInteger = Int(trimmedString) ?? 0
}
return turnStringToInteger
}
Now there are only 2 possibilities for type and the switch handles both.
You call it like this:
let result = integerTrim(integer: inputInt, type: .prefix, trimLength: 4)
... after a little refactoring:
func integerTrim(integer: Int, type: TrimType, trimLength: Int) -> Int {
let turnIntegerToString = String(integer)
let trimmedString: Substring
switch type {
case .prefix:
trimmedString = turnIntegerToString.prefix(trimLength)
case .suffix:
trimmedString = turnIntegerToString.suffix(trimLength)
}
return Int(trimmedString) ?? 0
}
There's a few ways to do this. The root of the problem is your turnIntegerToString lifetime is within the braces - and the return is outside the braces.
func integerTrim(integer:Int, type:String, trimLength:Int) -> Int {
var typeID = type
var turnStringToInteger: Int = 0
// If you don't want to assign it to zero (your nil coalesce implies we can) - instead use...
// var turnStringToInteger: Int! // However - this can crash since your if statement does not cover all situations
if typeID == "P" {
var turnIntegerToString = String(integer)
var trimmedString = turnIntegerToString.prefix(trimLength)
turnStringToIngeger = Int(trimmedString) ?? 0
}
else if typeID == "S" {
var turnIntegerToString = String(integer)
var trimmedString = turnIntegerToString.suffix(trimLength)
turnStringToIngeger = Int(trimmedString) ?? 0
}
return turnStringToInteger
}
func updateTotalScore() -> Int {
var totalScoreDefault = NSUserDefaults.standardUserDefaults()
var highScoreAB1 = defaults.integerForKey("highScoreAB1")
var highScoreAB2 = defaults.integerForKey("highScoreAB2")
var highScoreAB3 = defaults.integerForKey("highScoreAB3")
var highScoreAB4 = defaults.integerForKey("highScoreAB4")
var highScoreAB5 = defaults.integerForKey("HighScoreAB5")
var highScoreAB6 = defaults.integerForKey("highScoreAB6")
var highScoreAB7 = defaults.integerForKey("highScoreAB7")
totalScoreDefault =
(defaults.integerForKey("highScoreAB1") + defaults.integerForKey("highScoreAB2")) + (defaults.integerForKey("highScoreAB3") + defaults.integerForKey("highScoreAB4")) + (defaults.integerForKey("highScoreAB5") + defaults.integerForKey("highScoreAB6")) + defaults.integerForKey("highScoreAB7") }
Adding multiple keys to get a total score default throws the following error. I tried grouping them together into pairs, and that did not work. Thank you in advance. This is a continuation from a post from yesterday.
Just as an addition to Logan's answer, because you are saying that you have problems with "complex expression" compiler error. This should compile:
func updateTotalScore() -> Int {
let defaults = NSUserDefaults.standardUserDefaults()
let totalScoretDefault =
defaults.integerForKey("highScoreAB1") +
defaults.integerForKey("highScoreAB2") +
defaults.integerForKey("highScoreAB3") +
defaults.integerForKey("highScoreAB4") +
defaults.integerForKey("highScoreAB5") +
defaults.integerForKey("highScoreAB6") +
defaults.integerForKey("highScoreAB7")
return totalScoretDefault
}
It looks like you are trying to add all of the highscores up into one UserDefault named totalScoreDefault. If so, you need to be setting the totalScoreDefault like so:
default.setInteger(highScoreAB1 + ... + highScoreAB7, forKey: "totalScoreDefault")
// You can also consider adding all highScores up before
// this to make the setInteger portion look cleaner.
var totalScore = 0
for var i = 1; i < 8; i++ {
totalScore += defaults.integerForKey("highScoreAB\(i)")
}
defaults.setInteger(totalScore, forKey: "totalScoreDefault")
I've write a simple code:
extension String {
func trailingSpaces (width: Int) -> String {
var s = "\(self)"
for i in count(s)..<width {
s = s + " "
}
return s
}
func leadingSpaces (width: Int) -> String {
var s = "\(self)"
for i in count(s)..<width {
s = " " + s
}
return s
}
}
class ViewController: UIViewController {
var users = ["Marco", "Gianni", "Antonio", "Giulio", "Franco"]
var ages = [29, 45, 17, 33, 37]
override func viewDidLoad() {
super.viewDidLoad()
var merged = [String: Int] ()
var totalAge = 0.0
for var i = 0; i < ages.count; i++ {
merged[users[i]] = ages[i]
}
for user in sorted(merged.keys) {
let age = merged[user]
totalAge += Double(age!)
let paddedUser = user.trailingSpaces(10)
let paddedAge = "\(age)".leadingSpaces(3)
println("\(paddedUser) \(age!)")
}
println("\n\(merged.count) users")
println("average age: \(totalAge / Double(merged.count))")
}
}
but I can't make it work the leadingSpaces function and I can't understand the reason, it's quite identical to the other extension func that works.
It give the error
fatal error: Can't form Range with end < start
on runtime
in case you run into this kind of problem, always do a println() of the variable you are using
println("\(age)") right before let paddedAge = "\(age!)".leadingSpaces(3)
reveals the problem
age is an optional, meaning that you are trying to do the padding on a String which has this value "Optional(17)"
Thus, your count(s) is higher than 3, and you have an invalid range
Your variable age is not an Int - it's an optional - Int?. You know this already as you are unwrapping it in the lines totalAge += Double(age!) and println("\(paddedUser) \(age!)") - but you are not unwrapping it in the failing line let paddedAge = "\(age)".leadingSpaces(3). The string being passed to leadingSpaces is not "17", it's "Optional(17)", which is why your padding function is failing, as the length is greater than the requested width.
Having said that, as the commentator #milo256 points out, Swift can only iterate upwards, and so unless you put a check on width >= .count in your padding functions they will crash at some point.
When I try the following:
var somestring = "5"
var somenumber = 2
var newnumber:Int = Int(somestring) + somenumber
I get this error:
binary operator '+' cannot be applied to two Int operands
What am I doing wrong? Shouldn't '+' be valid for adding two Ints?
That's a really weird error message. The actual problem is that you can't simply construct Ints from Strings. The proper way to do that is with the toInt method like so:
var newnumber:Int = something.toInt()! + somenumber
Notice that toInt returns an optional that's unwrapped with !. If you're not sure the string represents an integer, error handling needs to be added as well.
You should consider using the nil coalescing operator "??" to return 0 instead of nil when trying to extract the value from your string:
let someString = "5"
let someNumber = 2
let newNumber = (someString.toInt() ?? 0) + someNumber
println(newNumber) // 7
let anotherString = "a"
let anotherNumber = (anotherString.toInt() ?? 0) + someNumber
println(anotherNumber) // 2
update: Xcode 7.1.1 • Swift 2.1
let someString = "5"
let someNumber = 2
let newNumber = (Int(someString) ?? 0) + someNumber
print(newNumber) // 7
let anotherString = "a"
let anotherNumber = (Int(anotherString) ?? 0) + someNumber // 2