Surprise results iterating over an array with an optional in Swift - swift

I was surprised that this swift code behaves nicely:
let values = ["Hello", "Test"]
var count = 0
for string: String in values {
count = count + 1
print("count is: ", count)
print(string)
}
with output of:
count is: 1
Hello
count is: 2
Test
but making the String into String? creates an infinite loop.
let values = ["Hello", "Test"]
var count = 0
for string: String? in values {
count = count + 1
print("count is: ", count)
print(string)
}
with output of:
count is: 1
Optional("Hello")
count is: 2
Optional("Test")
count is: 3
nil
count is: 4
nil
count is: 5
nil
count is: 6
nil
count is: 7
nil
count is: 8
(ad infinitum)
Swift has been so good at catching weird code problems that I was surprised I could walk into such a mess without warning or error.
Is this really what one would expect from Swift 4? And if so, why?

To understand this issue it helps to recollect how for-in loops work:
for s in values {
print(s)
}
creates an iterator of the sequence, and calls the iterator's next() method until that returns nil:
var it = values.makeIterator()
while let s = it.next() {
print(s)
}
Your second version is equivalent to
var it = values.makeIterator()
while let s: String? = it.next() {
print(s)
}
and now the compiler warns:
warning: explicitly specified type 'String?' adds an additional level
of optional to the initializer, making the optional check always succeed
while let s: String? = it.next() {
^ ~~~~~~~ ~~~~~~~~~
String
So what happens here is that the String? returned from it.next()
is wrapped into a “nested optional” .some(it.next()) of type String??, which is then optionally bound to s: String?.
This does always succeed, because .some(it.next()) is not String??.none.
Therefore the loop never terminates.
One could argue that the compiler should warn as well for
for s: String? in values {
print(s)
}

Your "for" runs on indexes. Under the index in excess of the number of elements is nill.

There is no error. In the first function it will keep going until it no more strings.
But other hand the second function you set the string to optional so when its no more string it will still keep going. Because nil is a nil value and it’s not the same as nothing. You have nothing that ends that loop.

Related

pointer arrays to String

I need to have a pointer array like in C, in Swift.
The following code works:
let ptr = UnsafeMutableBufferPointer<Int32>.allocate(capacity: 5)
ptr[0] = 1
ptr[1] = 5
print(ptr[0], ptr[1]) // outputs 1 5
The following code, however, does not work:
let ptr = UnsafeMutableBufferPointer<String>.allocate(capacity: 5)
print(ptr[0]) // Outputs an empty string (as expected)
print(ptr[1]) // Just exits with exit code 11
When I do print(ptr[1]) in the swift REPL, I get the following output:
Execution interrupted. Enter code to recover and continue.
Enter LLDB commands to investigate (type :help for assistance.)
How can I create a C-like array with Strings (or any other reference type, as this also doesn't seem to work with classes).
What should I adjust?
You need to initialize the memory with valid String data.
let values = ["First", "Last"]
let umbp = UnsafeMutableBufferPointer<String>.allocate(capacity: values.count)
_ = umbp.initialize(from: values)
print(umbp.map { $0 })
umbp[0] = "Joe"
umbp[1] = "Smith"
print(umbp.map { $0 })
Prints:
["First", "Last"]
["Joe", "Smith"]

Cannot assign value of type 'Int' to type 'Int?'

I am trying to find out the count of first and last name value exit in an array of a and return a result as[String: Int] a count with the same key.
I am getting error on this line newResult[arg.key] = counts . Cannot assign value of type 'Int' to type 'Int?
func abbreviation(a:[String], b: [String : String]) ->[String : Int] {
let dict = b.reduce([String : Int]()){ (result, arg) in
var newResult = result
let counts = a.reduce(0) { (newcount, value) -> Int in
let count = newcount + (value.components(separatedBy:arg.value).count - 1)
return count
}
return newResult[arg.key] = counts
}
return dict
}
//result
let dict = abbreviation(a:["This is chandan kumar and chandan kumar and new chandan","check chandan kumar","non ame"], b:["first":"chandan","last":"kumar"])
The error message is so confusing, and you may need to be accustomed to take it as Swift cannot infer some types in this context.
With this line:
return newResult[arg.key] = counts
Do you know what is returned with this return-statement? It's a Void, also known as an empty tuple. (Void is the result type of assignment statement in Swift.) You might have expected newResult would be the result of the closure, but that sort of things would not happen unless you explicitly write return newResult.
Try changing the line in to the following:
newResult[arg.key] = counts
return newResult
You are trying to return the result of an assignment expression:
return newResult[arg.key] = counts
or maybe you are trying to assign to the result of a return statement? This line doesn't make sense which way you look at it. You should separate the two things you are doing:
newResult[arg.key] = counts
return newResult
It seems like in this situation, you should use the other overload of the reduce method - reduce(into:_:).
The reduce method you are currently using requires you to return a new value every time, but you are just adding a KVP to a dictionary i.e. modifying an existing value. This means that you are creating lots of copies of dictionaries. This is a good sign that reduce(into:_:) might be a better fit.
func abbreviation(a:[String], b: [String : String]) ->[String : Int] {
// notice the parameter label "into:"
let dict = b.reduce(into: [String : Int]()){ (result, arg) in
let counts = a.reduce(0) { (newcount, value) -> Int in
let count = newcount + (value.components(separatedBy:arg.value).count - 1)
return count
}
result[arg.key] = counts // with reduce(into:_:), you don't return anything, just modify the first argument
}
return dict
}

Swift 4 Substring Crash

I'm a little confused about the best practices for Swift 4 string manipulation.
How do you handle the following:
let str = "test"
let start = str.index(str.startIndex, offsetBy: 7)
Thread 1: Fatal error: cannot increment beyond endIndex
Imagine that you do not know the length of the variable 'str' above. And since 'start' is not an optional value, what is the best practice to prevent that crash?
If you use the variation with limitedBy parameter, that will return an optional value:
if let start = str.index(str.startIndex, offsetBy: 7, limitedBy: str.endIndex) {
...
}
That will gracefully detect whether the offset moves the index past the endIndex. Obviously, handle this optional however best in your scenario (if let, guard let, nil coalescing operator, etc.).
Your code doesn't do any range checking:
let str = "test"
let start = str.index(str.startIndex, offsetBy: 7)
Write a function that tests the length of the string first. In fact, you could create an extension on String that lets you use integer subscripts, and returns a Character?:
extension String {
//Allow string[Int] subscripting. WARNING: Slow O(n) performance
subscript(index: Int) -> Character? {
guard index < self.count else { return nil }
return self[self.index(self.startIndex, offsetBy: index)]
}
}
This code:
var str = "test"
print("str[7] = \"\(str[7])\"")
Would display:
str[7] = "nil"
##EDIT:
Be aware, as Alexander pointed out in a comment below, that the subscript extension above has up to O(n) performance (it takes longer and longer as the index value goes up, up to the length of the string.)
If you need to loop through all the characters in a string code like this:
for i in str.count { doSomething(string: str[i]) }
would have O(n^2) (Or n-squared) performance, which is really, really bad. in that case, you should instead first convert the string to an array of characters:
let chars = Array(str.characters)
for i in chars.count { doSomething(string: chars[i]) }
or
for aChar in chars { //do something with aChar }
With that code you pay the O(n) time cost of converting the string to an array of characters once, and then you can do operations on the array of characters with maximum speed. The downside of that approach is that it would more than double the memory requirements.

String convert to Int and replace comma to Plus sign

Using Swift, I'm trying to take a list of numbers input in a text view in an app and create a sum of this list by extracting each number for a grade calculator. Also the amount of values put in by the user changes each time. An example is shown below:
String of: 98,99,97,96...
Trying to get: 98+99+97+96...
Please Help!
Thanks
Use components(separatedBy:) to break up the comma-separated string.
Use trimmingCharacters(in:) to remove spaces before and after each element
Use Int() to convert each element into an integer.
Use compactMap (previously called flatMap) to remove any items that couldn't be converted to Int.
Use reduce to sum up the array of Int.
let input = " 98 ,99 , 97, 96 "
let values = input.components(separatedBy: ",").compactMap { Int($0.trimmingCharacters(in: .whitespaces)) }
let sum = values.reduce(0, +)
print(sum) // 390
For Swift 3 and Swift 4.
Simple way: Hard coded. Only useful if you know the exact amount of integers coming up, wanting to get calculated and printed/used further on.
let string98: String = "98"
let string99: String = "99"
let string100: String = "100"
let string101: String = "101"
let int98: Int = Int(string98)!
let int99: Int = Int(string99)!
let int100: Int = Int(string100)!
let int101: Int = Int(string101)!
// optional chaining (if or guard) instead of "!" recommended. therefore option b is better
let finalInt: Int = int98 + int99 + int100 + int101
print(finalInt) // prints Optional(398) (optional)
Fancy way as a function: Generic way. Here you can put as many strings in as you need in the end. You could, for example, gather all the strings first and then use the array to have them calculated.
func getCalculatedIntegerFrom(strings: [String]) -> Int {
var result = Int()
for element in strings {
guard let int = Int(element) else {
break // or return nil
// break instead of return, returns Integer of all
// the values it was able to turn into Integer
// so even if there is a String f.e. "123S", it would
// still return an Integer instead of nil
// if you want to use return, you have to set "-> Int?" as optional
}
result = result + int
}
return result
}
let arrayOfStrings = ["98", "99", "100", "101"]
let result = getCalculatedIntegerFrom(strings: arrayOfStrings)
print(result) // prints 398 (non-optional)
let myString = "556"
let myInt = Int(myString)

Swift - endIndex.predecessor()

Do these comments make any sense ?
Trying to figure out why it removes the character of my reversed given string, instead of the regular given string in this case.
import Foundation
extension String {
func reverseWords() -> String {
var result = ""
let words = self.componentsSeparatedByString(" ")
for thisWord in words.reverse() {
result += thisWord + " "
}
print("..\(result)..")
// Result is the words array ( contains self ) reversed with seperator " "
print("..\(self)..")
result.removeAtIndex(self.endIndex.predecessor())
// So here i am checking self within result?, and am removing the last index
// of my currently reversed given string inside result?
// I do result in my intended last space removal with result.endIndex but i'm
// wondering what happens in this case with the self.endIndex :)
return result
}
}
var str = "This string contains a few elements"
str.reverseWords()
self still refers to the original unreversed String.
The correct code would be:
result.removeAtIndex(result.endIndex.predecessor())
You should never use a String index for another string. If you are indexing result, you shouldn't use an index from self.
With a simple string you won't seem a difference but if you start adding multi-byte characters, e.g. emojis, your application can crash.
For example, using result += thisWord + "😀" would result in:
elements😀few😀a😀contains😀string�This😀
with self.endIndex
and
elements😀few😀a😀contains😀string😀This
with result.endIndex.
endIndex is the index past the end of the String. It works the same as count for arrays. count in arrays doesn't represent the last element, count - 1 represents the last element.
If your aim is to change the original String, you have to declare the method as mutating and assign to self, e.g.:
mutating func reverseWords() {
var result = ""
let words = self.componentsSeparatedByString(" ")
for thisWord in words.reverse() {
result += thisWord + " "
}
self = result
self.removeAtIndex(self.endIndex.predecessor())
}
although that's rather uncommon in functional programming.
Using self.endIndex.predecessor() to remove from result won't effect self (the "given" String).
The index is just an abstraction of a number. That number is based off the string self, rather than result
But ultimately, that index is used to remove from result, not from self.
So this is what's happening, which indeed results into the conclusion that it's quite idiotic to use the index of self.. ?
result.removeAtIndex(self.endIndex.predecessor()) // <-- Stupid move
// self.endIndex = 35
// self.endIndex predecessor = 34 ( last index in self )
// Removes index 34 out of result
// result.endIndex = 36
// results.endIndex predecessor = 35 ( the " " )
// Removes the one before the " " inside results