Swift optional assignment, deal with nil/empty string - swift

I'm trying to solve an string assignment problem in Swift:
Here we have a struct:
struct Student {
var name: String
var id: String
var mentor: String?
var grade: String?
}
And we want to parse it into a string, something like:
if (mentor != nil && grade != nil) {
return "Student info: name:" + name + " id:" + id + " mentor:" + mentor! + " grade:" + grade! + "."
} else if (mentor != nil) {
return "Student info: name:" + name + " id:" + id + " mentor:" + mentor! + "."
} else if (grade != nil) {
return "Student info: name:" + name + " id:" + id + " grade:" + grade! + "."
} else {
return "Student info: name:" + name + " id:" + id + "."
}
I'm new to the Swift, the code above is based on other language's experience, I'm wondering if there's any more concise way to achieve that in Swift? Like deal with optional toString(), if it's nil then return a empty string ""?

Alexander's answer is good, and makes good use of the higher order function map(), but it might be a bit over your head if you're just starting out. You could simplify your code quite a bit with judicious use of if let "optional binding":
var result = "Student info: name:" + name + " id:" + id
if let mentor = mentor {
result += " mentor:" + mentor
}
if let grade = grade {
result += " grade:" + grade
}
result += "."
return result
Or rewritten to avoid the + operator:
var result = "Student info: name:\(name) id:\(id)"
if let mentor = mentor {
result.append(" mentor:\(mentor)")
}
if let grade = grade {
result.append(" grade:\(grade)")
}
result.append(".")
return result

Here are the improvements I would make:
Firstly, you should remove the unnecessary parentheses around the if predicates. You may be used to them from other C-like languages, but in Swift they're just noise.
Secondly, you should replace string concatenation (+) with interpolation. It has much faster compile times (operator type inference slows the compiler substantially), and will soon (with the rework of string interpolation) have better runtime performance (no redundant string allocation).
Next, I would use Optional.map to construct all the parts of the sentence that are optional. Then, I would default them to the empty string, if they're nil.
Then, I would take all the segments of the string, and join them together with a space as a separator:
struct Student {
var name: String
var id: String
var mentor: String?
var grade: String?
}
extension Student: CustomStringConvertible {
var description: String {
let start = "Student info:"
let nameSegment = "name: \(self.name)"
let idSegment = "id: \(self.id)"
let mentorSegment = self.mentor.map { "mentor: \($0)" } ?? ""
let gradeSegment = self.grade.map { "grade: \($0)" } ?? ""
return [start, nameSegment, idSegment, mentorSegment, gradeSegment].joined(separator: " ")
}
}
print(Student(name: "Bob", id: "id123", mentor: "Mr. Mentor", grade: "123"))
// => Student info: name: Bob id: id123 mentor: Mr. Mentor grade: 123

Related

Is it possible to iterate over a tree and indent the results by level with "reduce()"?

I have a Class for storing a tree and want to make it printable.
final class Node<Value> : CustomStringConvertible {
var value : Value
private(set) var children : [Node]
var description: String {
value as! String + children.reduce("") { $0 + "\n" + $1.description }
}
//...
}
With my code, all levels start at the first column of each line. Is it possible to indent the results by level in this way (e.g. by 2 spaces per level)?
1
1.1
1.1.1
1.2
1.2.1
1.2.1.1
1.2.1.2
I know how to solve this with some lines of code. I only want to know if it is possible by using reduce() or something like that to code it in one line.
You can achieve that with a helper method which takes a “level” parameter and calls itself recursively on each child node with an increased level:
private func toString(level: Int) -> String {
String(repeating: " ", count: level) + "\(value)"
+ children.reduce("") { $0 + "\n" + $1.toString(level: level + 2) }
}
var description: String {
self.toString(level: 0)
}

Swift function not working inside another function

I'm new on this site but I've been struggling for several days about this issue I found. I wrote this code in order to solve a challenge of the site Codewars; the challenge consists in calculate the mean and the variance from some data about some fictional rainfalls (I attach the complete page on the bottom). In order to end this challenge I created a function to convert the data from this useless string into an array of Doubles. The weird thing is that the function if called outside the main one works properly but inside returns an empty array. I have no idea why is happening this. Thank you very much for every effort you'll put trying to explain me this.
This is the first part of the Codewars page that explain the callenge
This is the second one
//
// main.swift
// Prova
//
// Created by Lorenzo Santini on 13/06/18.
// Copyright © 2018 Lorenzo Santini. All rights reserved.
//
import Foundation
func mean(_ d: String,_ town: String) -> Double {
let arrayOfValues = obtainArrayOfMeasures(d, town)
var sum: Double = 0
for element in arrayOfValues {
sum += element
}
return sum / Double(arrayOfValues.count)
}
func variance(_ d: String,_ town: String) -> Double {
let meanValue: Double = mean(d, town)
//Here is the problem: when this function is called instead of returning the array containg all the measures for the selected city it returns an empty array
var arrayOfValues = obtainArrayOfMeasures(d, town)
var sum: Double = 0
for element in arrayOfValues {
sum += pow((element - meanValue), 2)
}
return sum / Double(arrayOfValues.count)
}
func isInt(_ char: Character) -> Bool {
switch char {
case "1","2","3","4","5","6","7","8","9":
return true
default:
return false
}
}
func obtainArrayOfMeasures(_ d: String,_ town: String) -> [Double]{
//The first array stores the Data string divided for city
var arrayOfString: [String] = []
//The second array stores the measures of rainfall of the town passed as argument for the function
var arrayOfMeasures: [Double] = []
//Split the d variable containg the data string in separated strings for each town and add it to the arrayOfString array
repeat {
let finalIndex = (data.index(of:"\n")) ?? data.endIndex
arrayOfString.append(String(data[data.startIndex..<finalIndex]))
let finalIndexToRemove = (data.endIndex == finalIndex) ? finalIndex : data.index(finalIndex, offsetBy: 1)
data.removeSubrange(data.startIndex..<finalIndexToRemove)
} while data.count != 0
//Find the string of the town passed as argument
var stringContainingTown: String? = nil
for string in arrayOfString {
if string.contains(town) {stringContainingTown = string; print("true")}
}
if stringContainingTown != nil {
var stringNumber = ""
var index = 0
//Add to arrayOfMeasures the measures of the selected town
for char in stringContainingTown! {
index += 1
if isInt(char) || char == "." {
stringNumber += String(char)
print(stringNumber)
}
if char == "," || index == stringContainingTown!.count {
arrayOfMeasures.append((stringNumber as NSString).doubleValue)
stringNumber = ""
}
}
}
return arrayOfMeasures
}
var data = "Rome:Jan 81.2,Feb 63.2,Mar 70.3,Apr 55.7,May 53.0,Jun 36.4,Jul 17.5,Aug 27.5,Sep 60.9,Oct 117.7,Nov 111.0,Dec 97.9" + "\n" +
"London:Jan 48.0,Feb 38.9,Mar 39.9,Apr 42.2,May 47.3,Jun 52.1,Jul 59.5,Aug 57.2,Sep 55.4,Oct 62.0,Nov 59.0,Dec 52.9" + "\n" +
"Paris:Jan 182.3,Feb 120.6,Mar 158.1,Apr 204.9,May 323.1,Jun 300.5,Jul 236.8,Aug 192.9,Sep 66.3,Oct 63.3,Nov 83.2,Dec 154.7" + "\n" +
"NY:Jan 108.7,Feb 101.8,Mar 131.9,Apr 93.5,May 98.8,Jun 93.6,Jul 102.2,Aug 131.8,Sep 92.0,Oct 82.3,Nov 107.8,Dec 94.2" + "\n" +
"Vancouver:Jan 145.7,Feb 121.4,Mar 102.3,Apr 69.2,May 55.8,Jun 47.1,Jul 31.3,Aug 37.0,Sep 59.6,Oct 116.3,Nov 154.6,Dec 171.5" + "\n" +
"Sydney:Jan 103.4,Feb 111.0,Mar 131.3,Apr 129.7,May 123.0,Jun 129.2,Jul 102.8,Aug 80.3,Sep 69.3,Oct 82.6,Nov 81.4,Dec 78.2" + "\n" +
"Bangkok:Jan 10.6,Feb 28.2,Mar 30.7,Apr 71.8,May 189.4,Jun 151.7,Jul 158.2,Aug 187.0,Sep 319.9,Oct 230.8,Nov 57.3,Dec 9.4" + "\n" +
"Tokyo:Jan 49.9,Feb 71.5,Mar 106.4,Apr 129.2,May 144.0,Jun 176.0,Jul 135.6,Aug 148.5,Sep 216.4,Oct 194.1,Nov 95.6,Dec 54.4" + "\n" +
"Beijing:Jan 3.9,Feb 4.7,Mar 8.2,Apr 18.4,May 33.0,Jun 78.1,Jul 224.3,Aug 170.0,Sep 58.4,Oct 18.0,Nov 9.3,Dec 2.7" + "\n" +
"Lima:Jan 1.2,Feb 0.9,Mar 0.7,Apr 0.4,May 0.6,Jun 1.8,Jul 4.4,Aug 3.1,Sep 3.3,Oct 1.7,Nov 0.5,Dec 0.7"
var prova = variance(data, "London")
The problem is that func obtainArrayOfMeasures modifies the global data
variable. When called the second time, data is an empty string.
An indicator for this problem is also that making the global data variable constant
let data = "Rome:..."
causes a compiler error at
data.removeSubrange(data.startIndex..<finalIndexToRemove)
// Cannot use mutating member on immutable value: 'data' is a 'let' constant
An immediate fix would be to operate on a local mutable copy:
func obtainArrayOfMeasures(_ d: String,_ town: String) -> [Double]{
var data = d
// ...
}
Note however that the function can be simplified to
func obtainArrayOfMeasures(_ d: String,_ town: String) -> [Double] {
let lines = d.components(separatedBy: .newlines)
guard let line = lines.first(where: { $0.hasPrefix(town)}) else {
return [] // No matching line found.
}
let entries = line.components(separatedBy: ",")
let numbers = entries.compactMap { Double($0.filter {".0123456789".contains($0) })}
return numbers
}
without mutating any values. You might also consider to return nil
or abort with fatalError() if no matching entry is found.

Swift- how to initialize struct instance with function or other pattern

This is a dumb example, but I can't think of the right way to avoid repeating myself when I try to initialize my struct instances below. Notice how they get the same initializer (not sure if that's the right phrase), but what would be another way to do this so i'm giving it a function or something like that instead of the same struct.init(...)?
struct InnerSt {
var a: String
var b: String
}
var myStructs: [InnerSt] = []
func assignVal() {
for item in ["dog", "cat", "fish"] {
let a: String = "I'm a"
var pets: String
let inner: InnerSt = InnerSt.init(a: a, b: item)
switch item {
case "dog":
pets = "hairy"
//print(inner.a + " " + inner.b + " and I'm " + pets) //this is like what I want to repeatedly do without the repetition
myStructs.append(inner) //this works nicely but obviously I miss adding the pets variable
case "cat":
pets = "furry"
//print(inner.a + " " + inner.b + " and I'm " + pets)
myStructs.append(inner)
case "fish":
pets = "scaly"
//print(inner.a + " " + inner.b + " and I'm " + pets)
myStructs.append(inner)
default: ()
}
}
}
assignVal()
print(myStructs)
To avoid writing a bunch of initialisers you could simply change your implementation as follows:
func assignVal() {
let a = "I'm a "
for item in [1, 2] {
let temp = InnerSt.init(a: a, b: item)
print(temp)
}
}
Basically, you do not need to switch because item is being assigned as you loop. It will be assigned the value of 1 on the first iteration and 2 on the second.
The benefits are:
The InnerSt initialiser is written once (even though it is called multiple times).
If your array [1, 2] grows (to say [1, 2, 3]) you would not need to add new case to your switch.
A few side notes that helped me in the beginning:
InnerSt.init(a: a, b: item) can be shortened to InnerSt(a: a, b: item). Nice for readability.
let a: String = "I'm a" can be shorted to let a = "I'm a". Swift has an excellent type inference system. In this case the complier will infer that a is of type String.
innserSt would be better named InnerSt. See Apple's excellent guidelines.
Revision after comments
Playground code:
var petsDescriptions: [String] = [] // array of string descriptions of the animals
var pets = ["dog", "cat", "fish", "deer"] // array of all animals
func assignVal() {
for pet in pets {
var surfaceFeeling: String = "" // variable to hold the surface feeling of the pet e.g. "hairy"
switch pet { // switch on the pet
case "dog":
surfaceFeeling = "hairy"
case "cat":
surfaceFeeling = "furry"
case "fish":
surfaceFeeling = "scaly"
default:
surfaceFeeling = "<unknown>"
}
let description = "I'm \(surfaceFeeling) and I'm a \(pet)" // construct the string description
petsDescriptions.append(description) // add it to the storage array
}
}
assignVal()
print(petsDescriptions)
Console output:
["I\'m hairy and I\'m a dog", "I\'m furry and I\'m a cat", "I\'m scaly and I\'m a fish", "I\'m <unknown> and I\'m a deer"]
Let me know if I answered your question correctly or need to add some more information.

Delete some lines from string

I have a string:
first line
second line
first line
first line
second line
first line
How can I remove secondlines from this string? Secondlines are always different, firsts too. Only division between them is \n\n.
import Foundation
let string = "first line\n"
+ "second line\n"
+ "\n"
+ "first line\n"
+ "\n"
+ "first line\n"
+ "second line\n"
+ "\n"
+ "first line"
func removeSecondLines1(string: String) -> String {
let tokens = string.components(separatedBy: "\n")
var deletedString = tokens[0]
for i in 1...tokens.count - 1 {
if tokens[i] == "" || tokens[i - 1] == "" {
deletedString = deletedString + "\n" + tokens[i]
}
}
return deletedString
}
func removeSecondLines2(string: String) -> String {
let tokens = string.components(separatedBy: "\n\n")
var deletedTokens = [String]()
for token in tokens {
deletedTokens.append(token.components(separatedBy: "\n")[0])
}
return deletedTokens.joined(separator: "\n\n")
}
print(removeSecondLines1(string: string))
print(removeSecondLines2(string: string))
Both will output
first line
first line
first line
first line
Just for fun a solution with Regular Expression:
let string = "first line\nsecond line\n\nfirst line\n\nfirst line\nsecond line\n\nfirst line"
let pattern = "\\n[^\\n]+\\n\n"
let result = string.replacingOccurrences(of: pattern, with: "\n\n", options: .regularExpression)
print(result)

Type casting of double to String in swift

func getListing(var qty: Int) -> String{
if(qty < qtyInStock) {
return title + ", by " + author + " ($" + NSString(format:"%\(price).2d", price) + ")...In Stock"
}
else {
//return NSString(format: "%\(price)d") + author
return title + ", by " + author + " ($" + NSString(format:"%\(price).2d") + ")...Sold out"
}
}
Output : The Great Gatsby, by F. Scott Fitzgerald ($ 00)...In Stock"
I am unable to get the price value in the output. Also I don't to have an extra spacing in between the $ sign and the price of type double. Can you please help me with this.
Use %.2f instead of %\(price).2d
You should use NSNumberFormatter to format your numbers. Just create a read-only computed property Double extension as follow:
extension Double {
var currency: String {
let styler = NSNumberFormatter()
styler.minimumFractionDigits = 2
styler.maximumFractionDigits = 2
styler.numberStyle = .CurrencyStyle
styler.currencySymbol = "$"
return styler.stringFromNumber(self)!
}
}
199.99.currency // "$199.99"