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

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.

Related

How to get this to output with just one Swift print statement

This code produces the specific output but not with only one statement.
import Cocoa
var myTeams = ["Titans": ["Bob","Joe","Billy","Dan","Jill"], "Hawks": ["Frank","John","Matthew","Eric","Jack"], "Eagles": ["Swen","Olie","Helga","Errin","Gloggler"], "Falsons": ["Clyde","Dale","Elden","Fred","Hank","Ike","Jake"]]
myTeams.count
for teams in myTeams.keys {
let matchTeam = teams //Current team being processed
let players = myTeams[matchTeam] //Players in current team
_ = Array(arrayLiteral: players) //Create an array of players
print("\(teams) members: ")
for i in players! { // Use ! to remove optional
print(i)
}
print(" \n ")
}
You can use reduce to build one string of the whole dictionary
print(myTeams.reduce(into: "") {
$0.append("\($1.0) members:\n\($1.1.joined(separator:"\n"))\n \n") })
struct Team: CustomStringConvertible {
let name: String
let players: [String]
var description: String {
name + " members: \n" + players.flatMap{ $0 + "\n"}
}
}
var myTeams = [Team(name: "Titans", players: ["Bob","Joe","Billy","Dan","Jill"]),
Team(name: "Hawks", players: ["Frank","John","Matthew","Eric","Jack"])]
myTeams.forEach { print($0) }
I would go for struct and utilize CustomStringConvertible to achieve one line goodness. Cheers !

Loop through multiple lists

I have three dictionaries structured as [Int : Int]. Each dictionary is associated with an object. The key of the dictionary is a value of the object, and the value of the dictionary is how many object of that key should exist. One object holds all of these objects. So it would look at little like this.
class Cart {
var fruit: Fruit?
var cereal: Cereal?
var juice: Juice?
}
class Food {
var key: Int?
var text: String?
}
class Fruit: Food ...
class Cereal: Food ...
class Juice: Food ...
The idea is that the user has entered values for each possible option in text boxes and then a dictionary for each object is made. I need to make carts for every object, and each cart needs to holdas many objects as it can. If I have 6 fruits, 5 cereals, and 6 juices, then there should be 6 carts, one of them missing a cereal.
I have everything working up to figuring out how to actually put them together. Here's my loop.
for (key, value) in fruitValues {
for _ in 0..<value {
print(realm.objects(Fruit.self).filter("value == \(key)"))
}
}
How can I best loop through one array and get values from the others?
Here is my attempt to solve this, not the most compact solution but I couldn't figure out anything shorter. Assume our 3 dictionaries from the users selection
let dictFruit = [1: 2, 2: 4]
let dictCereal = [3: 5]
let dictJuice = [4: 3, 5:2, 6: 1]
for this example I use a dictionary as my Food types storage
let products = [1: Fruit(key: 1), 2: Fruit(key: 2), 3: Cereal(key: 3), 4:Juice(key: 4), 5:Juice(key: 5), 6:Juice(key: 6)]
First step is to flatten out the dictionaries to arrays of Food objects which is done with this function
func expandSelected<T: Food>(from dict: [Int: Int]) -> [T] {
return dict.reduce(into: []) {
if let food = products[$1.key] as? T {
$0.append(contentsOf: Array(repeating: food, count: $1.value))
}
}
}
Which I then used
let fruits: [Fruit] = expandSelected(from: dictFruit)
let juices: [Juice] = expandSelected(from: dictJuice)
let cereals: [Cereal] = expandSelected(from: dictCereal)
then for the last part I used an old fashioned for loop to create the Cart items
var carts = [Cart]()
for i in 0..<max(fruits.count, juices.count, cereals.count) {
let cart = Cart()
cart.cereal = cereals.count > i ? cereals[i] : nil
cart.fruit = fruits.count > i ? fruits[i] : nil
cart.juice = juices.count > i ? juices[i] : nil
carts.append(cart)
}

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.

error: instance member 'tomato' cannot be used on type 'hamburger'

1.what I code
class hamburger {
var tomato: String
var patty: String
var bread: String
var number: Int
init(_ tomato: String, _ patty: String, _ bread: String, _ number: Int) {
self.tomato = tomato
self.patty = patty
self.bread = bread
self.number = number
}
init() {
self.tomato = "tomato"
self.patty = "patty"
self.bread = "bread"
self.number = 10
}
}
let sandwich = hamburger("texas" , "iii" , "iii" , 10)
print(hamburger.tomato)
2.error message
Playground execution failed:
error: dotinstall.playground:342:7: error: instance member 'tomato'
cannot be used on type 'hamburger'
print(hamburger.tomato)
^~~~~~~~~ ~~~~~~
3.The sample I followed
enter code here// Class
class User {
let name: String // property
var score: Int
init(name: String, score: Int) {
init(_ name: String, _ score: Int) {
self.name = name
self.score = score
}
init() {
self.name = "bob"
self.score = 30
}
}
//let tom = User(name: "tom", score: 23)
let tom = User("tom", 23)
print(tom.name)
print(tom.score)
let bob = User()
print(bob.name)
print(bob.score)
I have coded like 1 following 3, but I got a error message like 2.
what I did to solve
・anyway follow this sample to be like same
・studied a basic of class syntax, initializer, instance on website
・was looking for a mistypes
・I checked the order of property
I don't why it is not worked even if I just follow the sample code.
please give me tips on the solution.
thanks
You're making a mistake regarding object oriented programming. With hamburger.tomato you try to access the property tomato of the Class Hamburger, not the Object, which is sandwich here. So the solution would be:
print(sandwich.tomato)
In the future, what you might want to do is take a look at styling your code better. Classes(Hamburger) are written starting with an uppercased letter, while the objects, or instances(sandwich), of the Classes are written starting with a lowercased letter.

Join strings in a block

I have a block that updates the view for each String. In its object class I pass it by:
func eachFeaturesSection(block: ((String?) -> Void)?) {
propertyFeatures.forEach { feature in
guard let feature = feature as? RealmString else {
return
}
let features = feature.stringValue
block?(features)
}
}
and I will get it in ViewController, by:
listing!.eachFeaturesSection({ (features) in
print(features)
self.facilities = features!
})
So it will print as:
Optional("String 1")
Optional("String 2")
and self.facilities will be set to latest value which is self.facilities = "String 2"
cell.features.text = features // it will print String 2
So, how can I achieve to join all strings together in one string such as self.facilities = "String 1, String 2". I used .jointString does not work. Thank you for any help.
Maybe you could add them to an array of String elements and then, when done, call joined on that array.
So something like this in your ViewController:
var featuresArray = [String]()
listing!.eachFeaturesSectionT({ (features) in
print(features)
featuresArray.append(features!)
})
//Swift 3 syntax
cell.features.text = featuresArray.joined(separator: ", ")
//Swift 2 syntax
cell.features.text = featuresArray.joinWithSeparator(", ")
Hope that helps you.
self.facilities = features! is doing nothing but keeps updating the value every iteration
Change the line self.facilities = features! to self.facilities += features! or self.facilities = self.facilities + ", " + features!
Here's how I'd do it (assuming your propertyFeatures is an array of RealmString):
Swift 3:
let string = (propertyFeatures.map { $0.stringValue }).joined(separator: ", ")
Swift 2:
let string = (propertyFeatures.map { $0.stringValue }).joinWithSeparator(", ")