Error handling in Swift non-optional-valued functions [duplicate] - string

This question already has an answer here:
Trim end off of string in swift, getting error at runtime
(1 answer)
Closed 7 years ago.
Trying to get to grips with Swift programming, I wrote the following:
var s : String = "dog"
var i1 : String.Index = advance(s.startIndex, 2)
var t1 : String = s.substringToIndex(i1)
Executing this code in a playground, t1 has the value "do", as expected. However, if I try to construct an index that exceeds the string's length, this happens:
var s : String = "dog"
var i2 : String.Index = advance(s.startIndex, 4)
var t2 : String = s.substringToIndex(i2)
This time, the line var i2 ... shows the error
Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP,subcode=0x0).
I read the Swift documentation, but the entry for String.substringToIndex reads in its entirety:
func substringToIndex(index: String.Index) -> String
[Foundation]
Returns a new string containing the characters of the String up to, but not including, the one at a given index.
The result is not optional, nor does the function possess an error parameter or return an empty string in case of faulty arguments.
I don't know how to prevent this by not creating an index in the first place, because String does not have a length or count property.
Since Swift does not have exception handling, how can programs recover from errors like this?
This is on OS X 10.10.2, Xcode 6.2.

The error is in advance(s.startIndex, 4) as you cannot advance eyond the end index:
1> var s = "dog"
s: String = "dog"
2> var i1 = advance(s.startIndex, 4)
fatal error: can not increment endIndex
i1: String.Index = {
_base = { /* ... */ }
/* ... */
}
Execution interrupted. Enter Swift code to recover and continue.
Enter LLDB commands to investigate (type :help for assistance.)
You avoid this by providing an end index as:
3> var i1 = advance(s.startIndex, 4, s.endIndex)
i1: String.Index = {
_base = { /* ... */ }
/* ... */
}
and then:
4> s.substringToIndex(i1)
$R0: String = "dog"
at least for Swift1.2, in Xcode6-Beta3.

Related

Grabbing first number in string after some keyword occurrence in C++ (Arduino)

I have a string coming from PC through serial to a microcontroller (Arduino), e.g.:
"HDD: 55 - CPU: 12.6 - Weather: Cloudy [...] $";
by this function I found:
String inputStringPC = "";
boolean stringCompletePC = false;
void serialEvent() {
while (Serial.available()) {
char inChar = (char)Serial.read();
inputStringPC += inChar;
if (inChar == '$') // end marker of the string
{
stringCompletePC = true;
}
}
}
I would like to extract the first number of it after the word HDD, CPU and also get the string after Weather (ie "cloudy"); my thinking is something like that:
int HDD = <function that does that>(Keyword HDD);
double CPU = <function that does that>(Keyword CPU);
char Weather[] = <function that does that>(Keyword Weather);
What is the right function to do that?
I looked into inputStringSerial.indexOf("HDD") but I am still a learner to properly understand what it does and don't know if theres a better function.
My approach yielded some syntax errors and confused me with the difference in usage between "String inputStringSerial" (class?) and "char inputStringSerial[]" (variable?). When I do 'string inputStringSerial = "";' PlatformIO complains that "string" is undefined. Any help to understand its usage here is greatly appreciated.
Thanks a bunch.
The String class provides member functions to search and copy the contents of the String. That class and all its member functions are documented in the Arduino Reference:
https://www.arduino.cc/reference/tr/language/variables/data-types/stringobject/
The other way a list of characters can be represented is a char array, confusingly also called a string or cstring. The functions to search and copy the contents of a char array are documented at
http://www.cplusplus.com/reference/cstring/
Here is a simple Sketch that copies and prints the value of the Weather field using a String object. Use this same pattern - with different head and terminator values - to copy the string values of the other fields.
Once you have the string values of HDD and CPU, you'll need to call functions to convert those string values into int and float values. See the String member functions toInt() and toFloat() at
https://www.arduino.cc/reference/en/language/variables/data-types/string/functions/toint/
or the char array functions atoi() and atof() at
http://www.cplusplus.com/reference/cstdlib/atoi/?kw=atoi
String inputStringPC = "HDD: 55 - CPU: 12.6 - Weather: Cloudy [...] $";
const char headWeather[] = "Weather: "; // the prefix of the weather value
const char dashTerminator[] = " -"; // one possible suffix of a value
const char dollarTerminator[] = " $"; // the other possible suffix of a value
void setup() {
int firstIndex; // index into inputStringPC of the first char of the value
int lastIndex; // index just past the last character of the value
Serial.begin(9600);
// find the Weather field and copy its string value.
// Use similar code to copy the values of the other fields.
// NOTE: This code contains no error checking for unexpected input values.
firstIndex = inputStringPC.indexOf(headWeather);
firstIndex += strlen(headWeather); // firstIndex is now the index of the char just past the head.
lastIndex = inputStringPC.indexOf(dollarTerminator, firstIndex);
String value = inputStringPC.substring(firstIndex, lastIndex);
Serial.print("Weather value = '");
Serial.print(value);
Serial.println("'");
}
void loop() {
// put your main code here, to run repeatedly:
}
When run on an Arduio Uno, this Sketch produces:
Weather value = 'Cloudy [...]'

Swift OS X String to Int Conversion Error

I'm having trouble converting a String to Int in my Swift OS X Xcode project. I have some data saved in a text file in a comma delimited format. The contents of the text file is below:
1,Cessna 172,3,54.4,124,38.6112
(and a line break at the end)
I read the text file and seperate it, first by \n to get each line by itself, and then by , to get each element by itself. The code to do this is below:
if let dir : NSString = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.AllDomainsMask, true).first {
let path = dir.stringByAppendingPathComponent("FSPassengers/aircraft.txt")
do {
let content = try NSString(contentsOfFile: path, encoding: NSUTF8StringEncoding)
if content != "" {
let astrContent:[String] = content.componentsSeparatedByString("\n")
for aeroplane in astrContent {
let aSeperated:[String] = aeroplane.componentsSeparatedByString(",")
print(aSeperated[0])
print(Int(aSeperated[0]))
//self.aAircraft.append(Aircraft(id: aSeperated[0], type: aSeperated[1], passengerCapacity: Int(aSeperated[2])!, cargoCapacityKg: Double(aSeperated[3])!, cruiseSpeed: Int(aSeperated[4])!, fuelLitresPerHour: Double(aSeperated[5])!))
}
}
}
catch {
print("Error")
}
}
The end result here will be to assign each record (each line of the text file) into the array aAircraft. This array is made up of a custom object called Aircraft. The custom class is below:
class Aircraft: NSObject {
var id:Int = Int()
var type:String = String()
var passengerCapacity:Int = Int()
var cargoCapacityKg:Double = Double()
var cruiseSpeed:Int = Int()
var fuelLitresPerHour:Double = Double()
override init() {}
init(id:Int, type:String, passengerCapacity:Int, cargoCapacityKg:Double, cruiseSpeed:Int, fuelLitresPerHour:Double) {
self.id = id
self.type = type
self.passengerCapacity = passengerCapacity
self.cargoCapacityKg = cargoCapacityKg
self.cruiseSpeed = cruiseSpeed
self.fuelLitresPerHour = fuelLitresPerHour
}
}
In the first code extract above, where I split the text file contents and attempt to assign them into the array, you will see that I have commented out the append line. I have done this to get the application to compile, at the moment it is throwing me errors.
The error revolves around the conversion of the String values to Int and Double values as required. For example, Aircraft.id, or aSeperated[0] needs to be an Int. You can see that I use the line Int(aSeperated[0]) to convert the String to Int in order to assign it into the custom object. However, this line of code is failing.
The two print statements in the first code extract output the following values:
1
Optional(1)
If I add a ! to the end of the second print statement to make them:
print(aSeperated[0])
print(Int(aSeperated[0])!)
I get the following output:
I understand what the error means, that it tried to unwrap an optional value because I force unwrapped it, and it couldn't find an Int value within the string I passed to it, but I don't understand why I am getting the error. The string value is 1, which is very clearly an integer. What am I doing wrong?
Because Casena 172 is not convertible to an Int. You also have other decimal numbers which you will lose precision when casting them to Int. Use NSScanner to create an initializer from a CSV string:
init(csvString: String) {
let scanner = NSScanner(string: csvString)
var type: NSString?
scanner.scanInteger(&self.id)
scanner.scanLocation += 1
scanner.scanUpToString(",", intoString: &type)
self.type = type as! String
scanner.scanLocation += 1
scanner.scanInteger(&self.passengerCapacity)
scanner.scanLocation += 1
scanner.scanDouble(&self.cargoCapacityKg)
scanner.scanLocation += 1
scanner.scanInteger(&self.cruiseSpeed)
scanner.scanLocation += 1
scanner.scanDouble(&self.fuelLitresPerHour)
}
Usage:
let aircraft = Aircraft(csvString: "1,Cessna 172,3,54.4,124,38.6112")
As #mrkxbt mentioned, the issue was related to the blank line after the data in the text file. The string was being split at the \n which was assigning two values into the array. The first value was a string containing the data and the second was an empty string, so obviously the second set of splitting (by ,) was failing. Amended and working code is below:
if let dir : NSString = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.AllDomainsMask, true).first {
let path = dir.stringByAppendingPathComponent("FSPassengers/aircraft.txt")
do {
let content = try NSString(contentsOfFile: path, encoding: NSUTF8StringEncoding)
if content != "" {
let astrContent:[String] = content.componentsSeparatedByString("\n")
for aeroplane in astrContent {
if aeroplane != "" {
let aSeperated:[String] = aeroplane.componentsSeparatedByString(",")
print(aSeperated[0])
print(Int(aSeperated[0])!)
self.aAircraft.append(Aircraft(id: Int(aSeperated[0])!, type: aSeperated[1], passengerCapacity: Int(aSeperated[2])!, cargoCapacityKg: Double(aSeperated[3])!, cruiseSpeed: Int(aSeperated[4])!, fuelLitresPerHour: Double(aSeperated[5])!))
}
}
}
}
catch {
print("Error")
}
}

Shuffling a string in Swift - swapping a location not supported [duplicate]

This question already has an answer here:
fatal error: swapping a location with itself is not supported with Swift 2.0
(1 answer)
Closed 7 years ago.
I have this code to shuffle a string in swift. For some reason it is giving an error in Xcode 7.1 "swapping a location with itself is not supported. I thought it was working ok. Any ideas where I have gone wrong much appreciated!
let selectedWord = word1 // word1 is a string
var chars = Array(selectedWord.characters)
chars.shuffleString()
let shuffledWord = String(chars)
word1 = shuffledWord
extension Array {
mutating func shuffleString() {
for index in 0..<(count - 1) {
let j = Int(arc4random_uniform(UInt32(count - index))) + index
swap(&self[index], &self[j]) // error on this line saying 'swapping a location with itself is not supported'
The swap function changed in a recent version of Xcode to prevent swapping a variable with itself. You can add a guard statement just before your swap to make sure index and j aren't the same:
extension Array {
mutating func shuffleString() {
for index in 0..<(count - 1) {
let j = Int(arc4random_uniform(UInt32(count - index))) + index
guard i != j else { continue }
swap(&self[index], &self[j])
}
}
}

Swift String.removeRange cannot compile

I don't understand what to do with the issue reported by the compiler. I tried to create a Range, but it says Index is not known:
//let range = matches.first!.range.location
let range = Range(
start:matches.first!.range.location,
end: matches.first!.range.location+matches.first!.range.length
)
id = text[range]
var t = text
t.removeRange(range)
return t
Compiler says: Cannot invoke 'removeRange' with an argument list of type '(Range)' on t.removeRange(range).
I'm pretty sure it's evident, but I lost a great deal of time on such a small issue… any help highly appreciated!
As your error says that:
Cannot invoke 'removeRange' with an argument list of type '(Range)'
Means there is a problem with your range instance type and removeRange function will only accept an argument with type Range<String.Index> and its syntax is :
/// Remove the indicated `subRange` of characters
///
/// Invalidates all indices with respect to `self`.
///
/// Complexity: O(\ `count(self)`\ ).
mutating func removeRange(subRange: Range<String.Index>)
And here is working example with removeRange:
var welcome = "hello there"
let range = advance(welcome.endIndex, -6)..<welcome.endIndex
welcome.removeRange(range)
println(welcome) //hello
Hope this will help.
Swift 2.2 example of removing first 4 characters:
let range = text.startIndex..<text.startIndex.advancedBy(4)
text.removeRange(range)
That first line feels verbose. I hope newer Swift versions improve upon it.
Here is the working equivalent snippet:
static func unitTest() {
let text = "a👿bbbbb🇩🇪c"
let tag = Tag(id: "🇩🇪")
tag.regex = "👿b+"
print ("Unit test tag.foundIn(\(text)) ? = \(tag.foundIn(text))")
}
func foundIn(text: String) -> (id:String, remainingText:String)? {
// if a regex is provided, use it to capture, and keep the capture as a tag ID
if let regex = regex {
let r = Regex(regex) // text =~ regex
let matches = r.matches(text)
if matches.count >= 1 {
let first = matches.first!.range
let start = advance(text.startIndex, first.location)
let end = advance(start, first.length-1)
let range = Range(start: start, end: end)
id = text[range]
var t = text
t.removeRange(range)
return (id, t)
}
return nil
}
else if let range = text.rangeOfString(id) {
var t = text
t.removeRange(range)
return (id, t)
}
else {
return nil
}
}
The unit test returns :
Unit test tag.foundIn(a👿bbbbb🇩🇪c) ? = Optional(("👿bbbbb", "a🇩🇪c"))

Remove last character from string. Swift language

How can I remove last character from String variable using Swift? Can't find it in documentation.
Here is full example:
var expression = "45+22"
expression = expression.substringToIndex(countElements(expression) - 1)
Swift 4.0 (also Swift 5.0)
var str = "Hello, World" // "Hello, World"
str.dropLast() // "Hello, Worl" (non-modifying)
str // "Hello, World"
String(str.dropLast()) // "Hello, Worl"
str.remove(at: str.index(before: str.endIndex)) // "d"
str // "Hello, Worl" (modifying)
Swift 3.0
The APIs have gotten a bit more swifty, and as a result the Foundation extension has changed a bit:
var name: String = "Dolphin"
var truncated = name.substring(to: name.index(before: name.endIndex))
print(name) // "Dolphin"
print(truncated) // "Dolphi"
Or the in-place version:
var name: String = "Dolphin"
name.remove(at: name.index(before: name.endIndex))
print(name) // "Dolphi"
Thanks Zmey, Rob Allen!
Swift 2.0+ Way
There are a few ways to accomplish this:
Via the Foundation extension, despite not being part of the Swift library:
var name: String = "Dolphin"
var truncated = name.substringToIndex(name.endIndex.predecessor())
print(name) // "Dolphin"
print(truncated) // "Dolphi"
Using the removeRange() method (which alters the name):
var name: String = "Dolphin"
name.removeAtIndex(name.endIndex.predecessor())
print(name) // "Dolphi"
Using the dropLast() function:
var name: String = "Dolphin"
var truncated = String(name.characters.dropLast())
print(name) // "Dolphin"
print(truncated) // "Dolphi"
Old String.Index (Xcode 6 Beta 4 +) Way
Since String types in Swift aim to provide excellent UTF-8 support, you can no longer access character indexes/ranges/substrings using Int types. Instead, you use String.Index:
let name: String = "Dolphin"
let stringLength = count(name) // Since swift1.2 `countElements` became `count`
let substringIndex = stringLength - 1
name.substringToIndex(advance(name.startIndex, substringIndex)) // "Dolphi"
Alternatively (for a more practical, but less educational example) you can use endIndex:
let name: String = "Dolphin"
name.substringToIndex(name.endIndex.predecessor()) // "Dolphi"
Note: I found this to be a great starting point for understanding String.Index
Old (pre-Beta 4) Way
You can simply use the substringToIndex() function, providing it one less than the length of the String:
let name: String = "Dolphin"
name.substringToIndex(countElements(name) - 1) // "Dolphi"
The global dropLast() function works on sequences and therefore on Strings:
var expression = "45+22"
expression = dropLast(expression) // "45+2"
// in Swift 2.0 (according to cromanelli's comment below)
expression = String(expression.characters.dropLast())
Swift 4:
let choppedString = String(theString.dropLast())
In Swift 2, do this:
let choppedString = String(theString.characters.dropLast())
I recommend this link to get an understanding of Swift strings.
Swift 4/5
var str = "bla"
str.removeLast() // returns "a"; str is now "bl"
This is a String Extension Form:
extension String {
func removeCharsFromEnd(count_:Int) -> String {
let stringLength = count(self)
let substringIndex = (stringLength < count_) ? 0 : stringLength - count_
return self.substringToIndex(advance(self.startIndex, substringIndex))
}
}
for versions of Swift earlier than 1.2:
...
let stringLength = countElements(self)
...
Usage:
var str_1 = "Maxim"
println("output: \(str_1.removeCharsFromEnd(1))") // "Maxi"
println("output: \(str_1.removeCharsFromEnd(3))") // "Ma"
println("output: \(str_1.removeCharsFromEnd(8))") // ""
Reference:
Extensions add new functionality to an existing class, structure, or enumeration type. This includes the ability to extend types for which you do not have access to the original source code (known as retroactive modeling). Extensions are similar to categories in Objective-C. (Unlike Objective-C categories, Swift extensions do not have names.)
See DOCS
Use the function removeAtIndex(i: String.Index) -> Character:
var s = "abc"
s.removeAtIndex(s.endIndex.predecessor()) // "ab"
Swift 4
var welcome = "Hello World!"
welcome = String(welcome[..<welcome.index(before:welcome.endIndex)])
or
welcome.remove(at: welcome.index(before: welcome.endIndex))
or
welcome = String(welcome.dropLast())
The easiest way to trim the last character of the string is:
title = title[title.startIndex ..< title.endIndex.advancedBy(-1)]
import UIKit
var str1 = "Hello, playground"
str1.removeLast()
print(str1)
var str2 = "Hello, playground"
str2.removeLast(3)
print(str2)
var str3 = "Hello, playground"
str3.removeFirst(2)
print(str3)
Output:-
Hello, playgroun
Hello, playgro
llo, playground
let str = "abc"
let substr = str.substringToIndex(str.endIndex.predecessor()) // "ab"
var str = "Hello, playground"
extension String {
var stringByDeletingLastCharacter: String {
return dropLast(self)
}
}
println(str.stringByDeletingLastCharacter) // "Hello, playgroun"
Short answer (valid as of 2015-04-16): removeAtIndex(myString.endIndex.predecessor())
Example:
var howToBeHappy = "Practice compassion, attention and gratitude. And smile!!"
howToBeHappy.removeAtIndex(howToBeHappy.endIndex.predecessor())
println(howToBeHappy)
// "Practice compassion, attention and gratitude. And smile!"
Meta:
The language continues its rapid evolution, making the half-life for many formerly-good S.O. answers dangerously brief. It's always best to learn the language and refer to real documentation.
With the new Substring type usage:
Swift 4:
var before: String = "Hello world!"
var lastCharIndex: Int = before.endIndex
var after:String = String(before[..<lastCharIndex])
print(after) // Hello world
Shorter way:
var before: String = "Hello world!"
after = String(before[..<before.endIndex])
print(after) // Hello world
Use the function advance(startIndex, endIndex):
var str = "45+22"
str = str.substringToIndex(advance(str.startIndex, countElements(str) - 1))
A swift category that's mutating:
extension String {
mutating func removeCharsFromEnd(removeCount:Int)
{
let stringLength = count(self)
let substringIndex = max(0, stringLength - removeCount)
self = self.substringToIndex(advance(self.startIndex, substringIndex))
}
}
Use:
var myString = "abcd"
myString.removeCharsFromEnd(2)
println(myString) // "ab"
Another way If you want to remove one or more than one character from the end.
var myStr = "Hello World!"
myStr = (myStr as NSString).substringToIndex((myStr as NSString).length-XX)
Where XX is the number of characters you want to remove.
Swift 3 (according to the docs) 20th Nov 2016
let range = expression.index(expression.endIndex, offsetBy: -numberOfCharactersToRemove)..<expression.endIndex
expression.removeSubrange(range)
The dropLast() function removes the last element of the string.
var expression = "45+22"
expression = expression.dropLast()
Swift 4.2
I also delete my last character from String (i.e. UILabel text) in IOS app
#IBOutlet weak var labelText: UILabel! // Do Connection with UILabel
#IBAction func whenXButtonPress(_ sender: UIButton) { // Do Connection With X Button
labelText.text = String((labelText.text?.dropLast())!) // Delete the last caracter and assign it
}
I'd recommend using NSString for strings that you want to manipulate. Actually come to think of it as a developer I've never run into a problem with NSString that Swift String would solve... I understand the subtleties. But I've yet to have an actual need for them.
var foo = someSwiftString as NSString
or
var foo = "Foo" as NSString
or
var foo: NSString = "blah"
And then the whole world of simple NSString string operations is open to you.
As answer to the question
// check bounds before you do this, e.g. foo.length > 0
// Note shortFoo is of type NSString
var shortFoo = foo.substringToIndex(foo.length-1)
Swift 3: When you want to remove trailing string:
func replaceSuffix(_ suffix: String, replacement: String) -> String {
if hasSuffix(suffix) {
let sufsize = suffix.count < count ? -suffix.count : 0
let toIndex = index(endIndex, offsetBy: sufsize)
return substring(to: toIndex) + replacement
}
else
{
return self
}
}
complimentary to the above code I wanted to remove the beginning of the string and could not find a reference anywhere. Here is how I did it:
var mac = peripheral.identifier.description
let range = mac.startIndex..<mac.endIndex.advancedBy(-50)
mac.removeRange(range) // trim 17 characters from the beginning
let txPower = peripheral.advertisements.txPower?.description
This trims 17 characters from the beginning of the string (he total string length is 67 we advance -50 from the end and there you have it.
I prefer the below implementation because I don't have to worry even if the string is empty
let str = "abc"
str.popLast()
// Prints ab
str = ""
str.popLast() // It returns the Character? which is an optional
// Print <emptystring>

Resources