Swift Class: Bank - Creating a Banking Application [closed] - swift

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
This is an assessment question, I need help with:
"
Create a Banking Application with a code containing:
Customer Class with :
ID:Int
name:String
balance:Double attributes
Bank class with
Customer array attribute
Constructor which receives count as parameter, and dynamically builds the customer array based on count:
On Creation, minimum balance of 500 should be assigned to the customer.
Customer ID must start from 1, and should increment by 1.
deposit(id:Int, amount:Double)->Void to deposit amount to ID.
withdraw(id:Int,amount:Double)->Void to withdraw from ID(maintain minimum balance of 500)
getCustomer(ID:Int)->Returns Customer of specified ID, nil otherwise.
The code I got so far:
import Foundation
/* Editable Code */
class Customer{
var id:Int
var name:String
var balance:Double
init(id:Int,name:String,balance:Double){
self.id = id
self.name = name
if(self.balance <= 0){
self.balance = 500
} else {
self.balance = balance
}
}
}
class Bank: Customer{
// create customer array with name customers
var customers:Array = [
Customer(id:123,name:"Ben",balance:12340.00),
Customer(id:124,name:"Tom",balance:12350.00),
Customer(id:125,name:"Jerry",balance:12389.90)
]
var count:Int = 1
init(count:Int){
self.count = count
}
//implement deposit, withdraw , and getCustomer
func deposit(id:Int,amount:Double) -> Void {
if(balance >= 0){
balance = balance + amount
}
}
func withdraw(id:Int,amount:Double) -> Void {
if amount > 0.0 {
if balance - amount >= 500.0 {
balance = balance - amount
}
}
}
func getCustomer(id:Int) -> Customer {
if(Customer[id] == id){
return Customer
}
}
}
/*End of Editable Code*/
/*Uneditable Code from Here*/
let stdout = ProcessInfo.processInfo.environment["OUTPUT_PATH"]!
FileManager.default.createFile(atPath: stdout, contents: nil, attributes: nil)
let fileHandle = FileHandle(forWritingAtPath: stdout)!
guard let custCount = Int((readLine()?.trimmingCharacters(in: .whitespacesAndNewlines))!)
else { fatalError("Bad input") }
guard let testCaseCount = Int((readLine()?.trimmingCharacters(in: .whitespacesAndNewlines))!)
else { fatalError("Bad input") }
var bank = Bank(count:custCount)
guard let id = Int((readLine()?.trimmingCharacters(in: .whitespacesAndNewlines))!)
else { fatalError("Bad input") }
for _ in 1...testCaseCount {
guard let ops = Int((readLine()?.trimmingCharacters(in: .whitespacesAndNewlines))!)
else { fatalError("Bad input") }
guard let amount = Double((readLine()?.trimmingCharacters(in: .whitespacesAndNewlines))!)
else { fatalError("Bad input") }
switch ops{
case 1 ://deposit
bank.deposit(id:id,amount:amount)
case -1 : //withdraw
bank.withdraw(id:id,amount:amount)
default : break
}
}
let cust:Customer! = bank.getCustomer(id:id)
fileHandle.write(String(bank.customers.count).data(using: .utf8)!)
fileHandle.write("\n".data(using: .utf8)!)
if cust != nil {
fileHandle.write(String(cust.balance).data(using: .utf8)!)
}
Part I am struggling is Customer Array Attribute above.
Need pointers on how to create the above said Customer array attribute
Please let me know how to modify my code to get achieve the above requirement.
Any help on this is much appreciated.
UPDATE 2) Added the solution in here, as it was closed by few, who thought I did not ask right question:
Used Bradley Mackey hint and worked on this solution.
Solution
import Foundation
/* Editable Code */
class Bank: Customer{
// create customer array with name customers
var customers:[Customer]
/*var customers:Array = [
Customer(id:1,name:"Ben",balance:12340.00),
Customer(id:2,name:"Tom",balance:12350.00),
Customer(id:3,name:"Jerry",balance:12389.90)
]*/
var count:Int = 0
init(count:Int){
NSLog("C: \(count)")
self.count = count
customers = []
var id:Int = 1
var name:String = ""
var balance:Double = 500.00
for i in 1...count {
id = i
customers.append(Customer(id:id,name:name,balance:balance))
}
super.init(id:id,name:name,balance:balance)
}
//implement deposit, withdraw , and getCustomer
func deposit(id:Int,amount:Double) -> Void {
NSLog("Deposit init: \(id) \(amount) \(count)")
if(id > count){
NSLog("\(id) > \(count)")
}
if(id == count){
customers[id-1].balance = customers[id-1].balance + amount
NSLog("\(id) = \(count) balance after deposit is \(customers[id-1].balance)")
}
if(id < count) {
for i in 1...id{
NSLog("for deposit \(i) \(customers[i-1].id)")
if(customers[i-1].id == id){
customers[i-1].balance = customers[i-1].balance + amount
NSLog("\(i)")
}
}
NSLog("\(id) < \(count) balance after deposit is \(customers[id].balance)")
}
NSLog("deposit last \(id) \(amount)")
}
func withdraw(id:Int,amount:Double) -> Void {
NSLog("withdraw: \(id) \(amount)")
if(id <= count){
for i in 1...id{
if(customers[i-1].id == id){
if((customers[i-1].balance - amount) > 500){
customers[i-1].balance = customers[i-1].balance - amount
NSLog("withdraw made \(amount) on \(i-1)")
}
}
}
}
NSLog("withdraw \(id) \(amount)")
}
func getCustomer(id:Int) -> Customer? {
NSLog("getcus \(id)")
/*if(id == 1){
return customers[id]
}*/
if(id > count){
return nil
}
if(id == count){
return customers[id-1]
}
if(id < count){
for i in 0...id {
if(customers[i].id == id){
return customers[i]
}
}
}
NSLog("Bal: \(customers[id].balance)")
return nil
}
}
/*End of Editable Code*/
/*Uneditable Code from Here*/
let stdout = ProcessInfo.processInfo.environment["OUTPUT_PATH"]!
FileManager.default.createFile(atPath: stdout, contents: nil, attributes: nil)
let fileHandle = FileHandle(forWritingAtPath: stdout)!
guard let custCount = Int((readLine()?.trimmingCharacters(in: .whitespacesAndNewlines))!)
else { fatalError("Bad input") }
guard let testCaseCount = Int((readLine()?.trimmingCharacters(in: .whitespacesAndNewlines))!)
else { fatalError("Bad input") }
var bank = Bank(count:custCount)
guard let id = Int((readLine()?.trimmingCharacters(in: .whitespacesAndNewlines))!)
else { fatalError("Bad input") }
for _ in 1...testCaseCount {
guard let ops = Int((readLine()?.trimmingCharacters(in: .whitespacesAndNewlines))!)
else { fatalError("Bad input") }
guard let amount = Double((readLine()?.trimmingCharacters(in: .whitespacesAndNewlines))!)
else { fatalError("Bad input") }
switch ops{
case 1 ://deposit
bank.deposit(id:id,amount:amount)
case -1 : //withdraw
bank.withdraw(id:id,amount:amount)
default : break
}
}
let cust:Customer! = bank.getCustomer(id:id)
fileHandle.write(String(bank.customers.count).data(using: .utf8)!)
fileHandle.write("\n".data(using: .utf8)!)
if cust != nil {
fileHandle.write(String(cust.balance).data(using: .utf8)!)
}

I'll give some general hints as this is an assessment.
The customer array attribute refers to this property on the Bank.
var customers:Array = [
Customer(id:123,name:"Ben",balance:12340.00),
Customer(id:124,name:"Tom",balance:12350.00),
Customer(id:125,name:"Jerry",balance:12389.90)
]
You will need to set this to be some customers that is passed to the init 'initializer' as it's called in Swift. To do this, use a FOR LOOP to figure out how many customers you need, then create a customer in each one, and append each one to the array. For example:
init(count:Int){
self.count = count
// first of all, clear the customers array
customers = []
for i in 1...count {
// CREATE THE CUSTOMER HERE
// ...
// use i as the customer id to create each customer,
// setting their initial balance to 500, then `append` to `customers`
}
}

Related

How to empty an appended array after a widget timeline update

I am transitioning from UIKit to SwiftUI in my app and am updating network calls to async await as well. I created a Large size widget that displays the weather for 7 airports. The airports identifiers are stored in an app group shared userdefatults container. I update the timeline every minute (just for testing, normally it would be every 20 minutes). Initially when the widget is selected and appears, all data is there and correct. After a timeline update the data updates, but not all the airports are returned and after two updates or so (not consistent), the screen goes blank. The userdefaults airports are updated from the main app and saved in the shared user defaults container and it calls WidgetCenter.shared.reloadAllTimelines. This is all working fine as I have another process that uses the same container for a small widget, but with only one airport returning data without the need for an appended array. If I remove the calls to empty the array, the data remains and doesn't go blank, but of course the array keeps appending. I've tried the removeAll() and [] to empty the array at different places in the code, but same result. I am trying to understand the flow in the async/await calls, but seem to be missing something here? Any help would be greatly appreciated. I've been googling and searching stack overflow for a month and don't really know how to solve this issue. Thanks in advance!
actor MetarService: NSObject, XMLParserDelegate, URLSessionDelegate, ObservableObject {
enum MetarFetcherError: Error {
case invalidServerResponse
case missingData
}
#Published var metarArray = [String]()
#Published var metarDataModel: [MetarDataModel] = []
var tempDataModel: [MetarDataModel] = []
func fetchMetars(metarAPTs: String) async throws -> [MetarDataModel] {
let wxUrl = URL(string: "https://www.aviationweather.gov/adds/dataserver_current/httpparam?dataSource=metars&requestType=retrieve&format=xml&hoursBeforeNow=3&mostRecent=true&stationString=" + metarAPTs)!
let (data, response) = try await URLSession.shared.data(from: wxUrl)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw MetarFetcherError.invalidServerResponse
}
guard let xml = SWXMLHash.parse(data) as XMLIndexer? else {
throw MetarFetcherError.missingData
}
noDataResponse = (xml["response"]["data"].element?.attribute(by: "num_results")?.text) ?? "0"
if noDataResponse == "1" && (xml["response"]["data"]["METAR"]["observation_time"].element?.text) != nil {
if (xml["response"]["data"]["METAR"]["station_id"].element?.text) != nil {
myairport = xml["response"]["data"]["METAR"]["station_id"].element!.text
} else {
myairport = "MSNG"
}
if (xml["response"]["data"]["METAR"]["flight_category"].element?.text) != nil {
myfltcat = xml["response"]["data"]["METAR"]["flight_category"].element!.text
} else {
myfltcat = "MISNG"
}
switch myfltcat {
case "VFR":
mymetarImage = "sun.max.circle.fill"
case "MVFR":
mymetarImage = "cloud.sun.circle.fill"
case "IFR":
mymetarImage = "cloud.fog.circle.fill"
case "LIFR":
mymetarImage = "smoke.circle.fill"
default:
mymetarImage = "person.crop.circle.badge.questionmark"
}
if (xml["response"]["data"]["METAR"]["observation_time"].element?.text) != nil {
myobstime = xml["response"]["data"]["METAR"]["observation_time"].element!.text as NSString
if myobstime.length < 16 {
myobstime = "MISNG"
} else {
myobstime = myobstime.substring(with: NSRange(location: 11, length: 5)) as NSString
}
}
if (xml["response"]["data"]["METAR"]["visibility_statute_mi"].element?.text) != nil {
myvis = xml["response"]["data"]["METAR"]["visibility_statute_mi"].element!.text
let intVis = (myvis as NSString) .integerValue
myvis = String(intVis) + "SM"
} else {
myvis = "0"
}
if (xml["response"]["data"]["METAR"]["wind_dir_degrees"].element?.text) != nil {
mywinddir = xml["response"]["data"]["METAR"]["wind_dir_degrees"].element!.text
if mywinddir.contains("VRB") {
mywinddir = "VRB"
} else
if mywinddir.count <= 2 && mywinddir.count > 0 {
mywinddir = "0" + mywinddir
}
} else {
mywinddir = "MISNG"
}
if (xml["response"]["data"]["METAR"]["wind_speed_kt"].element?.text) != nil {
mywindspd = xml["response"]["data"]["METAR"]["wind_speed_kt"].element!.text
if mywindspd == "0" {
mywind = "Calm"
} else if mywindspd.count == 1 {
mywindspd = "0" + mywindspd
mywind = mywinddir + "/" + mywindspd + "KT"
} else if mywindspd.count > 1 {
mywind = mywinddir + "/" + mywindspd + "KT"
}
} else {
mywind = "MISNG"
}
}
self.tempDataModel.append(MetarDataModel(metarImage: mymetarImage, mairport: myairport, mobstime: myobstime as String, mfltcat: myfltcat, mvis: myvis, mwind: mywind))
self.metarDataModel = self.tempDataModel
tempDataModel = []
return metarDataModel
}
func readMetarApts() -> [String] {
let defaults = UserDefaults(suiteName: "group.userdefaults.shared.FRAT")!
if ((defaults.value(forKey: "icaoIdent") as! String).isEmpty) {
defaultairport = "KSFO"
} else {
defaultairport = defaults.value(forKey: "icaoIdent") as! String
}
wxAirport1 = defaults.value(forKey: "wxAirport1") as! String
wxAirport2 = defaults.value(forKey: "wxAirport2") as! String
wxAirport3 = defaults.value(forKey: "wxAirport3") as! String
wxAirport4 = defaults.value(forKey: "wxAirport4") as! String
wxAirport5 = defaults.value(forKey: "wxAirport5") as! String
wxAirport6 = defaults.value(forKey: "wxAirport6") as! String
metarArray.append(defaultairport)
metarArray.append(wxAirport1)
metarArray.append(wxAirport2)
metarArray.append(wxAirport3)
metarArray.append(wxAirport4)
metarArray.append(wxAirport5)
metarArray.append(wxAirport6)
metarArray = metarArray.sorted()
let returnArray = metarArray
metarArray = []
return returnArray
}// end of readAirports function
nonisolated func getAirports() -> ([MetarDataModel] ){
// transData = []
Task{
let tempArray = await readMetarApts()
for apts in tempArray {
let zData = try await self.fetchMetars(metarAPTs: apts)
if zData .isEmpty {
let errorData = MetarDataModel(metarImage: "sun.max.circle.fill", mairport: "DATA", mobstime: "CHK", mfltcat: "MSNG", mvis: "WiFi", mwind: "")
tempData = [errorData]
} else {
transData.append(contentsOf: zData)
tempData = transData
} // else Closure
} //Task Closure
//transData.removeAll()
} // apts in tempArray Closure
tempData = transData
// transData = []
return tempData.sorted()
} // end of getAirports function
} // end of MetarService Class
I have tried different solutions found on stack overflow, reddit, medium and others. But no matter what approach I take, if I try and empty the appended array in preparation for the next timeline update, it will always eventually return without data. At first I thought it was the app group shared container losing reference as I got the much debated 'kCFPreferencesAnyUser, ByHost: Yes, Container: (null)): Using kCFPreferencesAnyUser with a container is only allowed for System Containers, detaching from cfprefsd' in the log, but Apple says this does not indicate that particular condition? And, I use the container elsewhere with no issues. I am new to the async await and the new URLSession.shared.data(from: ) and maybe I'm not understanding the flow and timing of how the data is fetched and returned? I just need to append the array, display the data, then empty the array and wait for the next timeline to fetch new data. I've put the removeAll() and tried [] as an alternative in many different places in my code (at the start of the function and at the end of the function). Stumped!!

Swift Completion Handler For Loop to be performed once instead of 10 times due to the loop

I I have a loop with a firestore query in it that is repeated 10 times. I need to call the (completion: block) after all the 10 queries completed; Here I have my code so that it performs the (completion: block) per query but this would be too heavy on the server and the user's phone. How can I change below to accomplish what I just described?
static func getSearchedProducts(fetchingNumberToStart: Int, sortedProducts: [Int : [String : Int]], handler: #escaping (_ products: [Product], _ lastFetchedNumber: Int?) -> Void) {
var lastFetchedNumber:Int = 0
var searchedProducts:[Product] = []
let db = Firestore.firestore()
let block : FIRQuerySnapshotBlock = ({ (snap, error) in
guard error == nil, let snapshot = snap else {
debugPrint(error?.localizedDescription)
return
}
var products = snapshot.documents.map { Product(data: $0.data()) }
if !UserService.current.isGuest {
db.collection(DatabaseRef.Users).document(Auth.auth().currentUser?.uid ?? "").collection(DatabaseRef.Cart).getDocuments { (cartSnapshot, error) in
guard error == nil, let cartSnapshot = cartSnapshot else {
return
}
cartSnapshot.documents.forEach { document in
var product = Product(data: document.data())
guard let index = products.firstIndex(of: product) else { return }
let cartCount: Int = document.exists ? document.get(DatabaseRef.cartCount) as? Int ?? 0 : 0
product.cartCount = cartCount
products[index] = product
}
handler(products, lastFetchedNumber)
}
}
else {
handler(products, lastFetchedNumber)
}
})
if lastFetchedNumber == fetchingNumberToStart {
for _ in 0 ..< 10 {
//change the fetching number each time in the loop
lastFetchedNumber = lastFetchedNumber + 1
let productId = sortedProducts[lastFetchedNumber]?.keys.first ?? ""
if productId != "" {
db.collection(DatabaseRef.products).whereField(DatabaseRef.id, isEqualTo: productId).getDocuments(completion: block)
}
}
}
}
as you can see at the very end I am looping 10 times for this query because of for _ in 0 ..< 10 :
if productId != "" {
db.collection(DatabaseRef.products).whereField(DatabaseRef.id, isEqualTo: productId).getDocuments(completion: block)
}
So I need to make the completion: block handler to be called only once instead of 10 times here.
Use a DispatchGroup. You can enter the dispatch group each time you call the async code and then leave each time it's done. Then when everything is finished it will call the notify block and you can call your handler. Here's a quick example of what that would look like:
let dispatchGroup = DispatchGroup()
let array = []
for i in array {
dispatchGroup.enter()
somethingAsync() {
dispatchGroup.leave()
}
}
dispatchGroup.notify(queue: .main) {
handler()
}

Returning a value from various loops within a function

I'm trying to return a realm object from a function that contains a serious of loops. The gist of the function is to create a randomly weighted generator using a switch statement to parse between various 'buckets' of lower and upper bound cumulative probabilities until a randomly generated double lands in one of these buckets, and thus returns the student object.
I originally was just changing a label when the correct student was selected, but I need to edit other parameters of the student outside of the function and thus I can't just return the name from the function anymore, but rather need to return the object. Old code is attached below. Only change I've made would be to add returned parameter to function name: func randomWeightedStudent() -> Student { ... } and instead of studentNameLabel.text = currentStudent.name I'd like to return currentStudent
func randomWeightedStudent() {
let randomValue = Double.random(in: 0.00 ... 1.00)
if let numberOfStudents = students?.count {
for i in 0 ... (numberOfStudents - 1) {
guard let currentStudent = students?[i] else { continue }
if i == 0 {
switch randomValue {
case (0 ... (currentStudent.prob)) :
studentNameLabel.text = currentStudent.name
default :
break
}
} else if i < numberOfStudents {
guard let lastStudent = students?[i-1] else { continue }
switch randomValue {
case (lastStudent.cumProb) ... (currentStudent.cumProb) :
studentNameLabel.text = currentStudent.name
default :
break
}
} else if i == numberOfStudents {
switch randomValue {
case (currentStudent.cumProb) ... 1 :
studentNameLabel.text = currentStudent.name
default :
break
}
}
}
}
}
As described above, I'd like to be able to say return currentStudent in place of studentNameLabel.text = currentStudent.name but I'm not having success with that. Error is generating that function needs a return value which makes me speculate that I need to somehow return the value up a serious of each loop
This is how I would implement the function to return a Student object or nil. I also made some other simplifications to the code to make it clearer.
func randomWeightedStudent() -> Student? {
let randomValue = Double.random(in: 0.00 ... 1.00)
guard let students = students else { return nil }
let numberOfStudents = students.count
for i in 0..<numberOfStudents {
let currentStudent = students[i]
if i == 0 {
switch randomValue {
case (0 ... (currentStudent.prob)) :
return currentStudent
default :
break
}
} else if i < numberOfStudents {
let lastStudent = students[i-1]
switch randomValue {
case (lastStudent.cumProb) ... (currentStudent.cumProb) :
return currentStudent
default :
break
}
} else if i == numberOfStudents {
switch randomValue {
case (currentStudent.cumProb) ... 1 :
return currentStudent
default :
break
}
}
}
return nil
}

Realm object predicate filter is invalid

I use predicate to filter the converstions of models. I got the wrong result with Realm filter (0 models though there should be one). After that, I did another check and she showed me that there is one model with these criteria. Please tell me what may be wrong here.
let checkConversations = Array(realm.objects(ChatConversationModel.self)).filter({ $0.lastMessage != nil && $0.toDelete == false })
debugPrint("checkConversations", checkConversations.count) received one model (this is the right result).
var conversations = Array(realm.objects(ChatConversationModel.self).filter("lastMessage != nil && toDelete == false"))
debugPrint("conversations", conversations.count) I did not receive any models at all
Models:
class ChatConversationModel: Object, Mappable {
/// oneToOne = friend
enum ChatType {
case oneToOne, group, nonFriend
var index: Int {
switch self {
case .oneToOne:
return 0
case .group:
return 1
case .nonFriend:
return 2
}
}
}
#objc dynamic var id = ""
#objc dynamic var typeIndex = ChatType.oneToOne.index
#objc dynamic var lastMessage: ChatMessageRealmModel?
#objc dynamic var lastActivityTimeStamp = 0.0
#objc dynamic var modelVersion = AppStaticSettings.versionNumber
let createTimestamp = RealmOptional<Double>()
// for group chat
#objc dynamic var groupConversationOwnerID: String?
/// for group chat equal card photos
#objc dynamic var cardInfo: ChatConversationCardInfoModel?
// Local
#objc dynamic var toDelete = false
override class func primaryKey() -> String? {
return "id"
}
convenience required init?(map: Map) {
self.init()
}
func mapping(map: Map) {
if map.mappingType == .fromJSON {
id <- map["id"]
typeIndex <- map["typeIndex"]
lastMessage <- map["lastMessage"]
lastActivityTimeStamp <- map["lastActivityTimeStamp"]
createTimestamp.value <- map["createTimestamp"]
modelVersion <- map["modelVersion"]
// for group chat
cardInfo <- map["cardInfo"]
groupConversationOwnerID <- map["groupConversationOwnerID"]
} else {
id >>> map["id"]
typeIndex >>> map["typeIndex"]
lastMessage >>> map["lastMessage"]
lastActivityTimeStamp >>> map["lastActivityTimeStamp"]
createTimestamp.value >>> map["createTimestamp"]
modelVersion >>> map["modelVersion"]
// for group chat
cardInfo >>> map["cardInfo"]
groupConversationOwnerID >>> map["groupConversationOwnerID"]
}
}
}
Update: When I receive actual conversations, I start comparing which already exists in the application. And looking for ids which are not in the result. Next, I find these irrelevant conversations and put toDelete = false, in order to "safely" delete inactive conversations. Since I listened to a podcast with one of Realm's developers, he advised not to delete an object that can be used. And since when receiving results with backend, any inactive conversation can be active again, so I chose this method. You can view the code for these functions.
private func syncNewConversations(_ conversations: [ChatConversationModel], userConversations: [ChatUserPersonalConversationModel], completion: ((_ error: Error?) -> Void)?) {
DispatchQueue.global(qos: .background).async {
let userConversationsIDs = userConversations.map { $0.id }
DispatchQueue.main.async {
do {
let realm = try Realm()
let userConversationPredicate = NSPredicate(format: "NOT id IN %#", userConversationsIDs)
let notActualUserConversations = realm.objects(ChatUserPersonalConversationModel.self).filter(userConversationPredicate)
let filteredNotActualUserConversations = Array(notActualUserConversations.filter({ $0.lastActivityTimeStamp < $0.removedChatTimeStamp }))
let notActualConversationIDs = filteredNotActualUserConversations.map { $0.id }
let notActualConversationPredicate = NSPredicate(format: "id IN %#", notActualConversationIDs)
let notActualConversations = realm.objects(ChatConversationModel.self).filter(notActualConversationPredicate)
let notActualMessagesPredicate = NSPredicate(format: "conversationID IN %#", notActualConversationIDs)
let notActualMessages = realm.objects(ChatMessageRealmModel.self).filter(notActualMessagesPredicate)
try realm.write {
realm.add(userConversations, update: true)
realm.add(conversations, update: true)
for notActualUserConversation in notActualUserConversations {
notActualUserConversation.toDelete = true
}
for notActualConversation in notActualConversations {
notActualConversation.toDelete = true
}
for notActualMessage in notActualMessages {
notActualMessage.toDelete = true
}
completion?(nil)
}
} catch {
debugPrint(error.localizedDescription)
completion?(error)
}
}
}
}

Swift Dijkstra Algorithm Error (EXC_BAD_INSTRUCTION)

So I've built a swift playground that uses Dijkstra's algorithm to find the shortest route. However, I can't seem to manipulate my txt file so that my function will work in every case. It only works for a select few pathways. Whenever I map out a pathway I believe should work, it responds with: Execution was interrupted, reason: EXC_BAD_INSTRUCTION(code=EXC_I386_INVOP, subcode=0x0).
How can I manipulate my txt file or my function/file manipulation to take my txt file input? (Where error occurred is marked -- near the bottom)
Usually the error occurs when trying to build a route backwards.
Ex: a1 to a3 works
Ex: a3 to a1 does not work
Code:
import Foundation
// Extensions
extension Array {
func each<U>(closure:(Element)->U?)->U? {
for i in self {
let returnVal = closure(i)
if (returnVal != nil) { return returnVal }
}
return nil
}
}
extension Int {
func times(closure:(Int)->Void) {
for i in 0..<self { closure(i) }
}
}
// Structs
struct Edge {
var wt: Double
var desV: Room
}
struct Graph { var vertices:[Room] }
// Room Class
class Room: Hashable {
var name: String?
var neighbors: [Edge] = []
var hashValue: Int {
get { return name!.hashValue }
}
init(){}
convenience init(name:String) {
self.init()
self.name = name
}
func distanceToRoom(targetRoom:Room) -> Edge? {
return self.neighbors.each({ (edge:Edge) -> Edge? in
if (edge.desV == targetRoom) {
return edge
}
return nil
})
}
}
// Functions
func == (lhs:Room, rhs:Room) -> Bool { return lhs.hashValue == rhs.hashValue }
func say( a:String ) { print( a, terminator:"") }
func dijkstra(graph:Graph, target:Room) -> [Room:Room] {
var queue = graph.vertices
var distances:[Room:Double] = [:]
var previousPaths:[Room:Room] = [:]
let currentRoom:Room = queue[0]
queue.each {(element:Room) -> Void? in
distances[element] = Double.infinity
previousPaths[element] = nil
return nil
}
distances[currentRoom] = 0
while (queue.count > 0) {
var closestNode:Room? = nil
let wt:Double = Double.infinity
queue.each({ (Room:Room) -> Void? in
if (closestNode == nil || wt < distances[Room]!) {
closestNode = Room
}
return nil
})
if (closestNode! == target) {
return previousPaths
}
let nodeIndex:Int? = queue.indexOf(closestNode!)
queue.removeAtIndex(nodeIndex!)
if (closestNode?.neighbors != nil && closestNode?.neighbors.count > 0) {
closestNode?.neighbors.each({(neighbor:Edge) -> Void? in
let wt = distances[closestNode!]! + closestNode!.distanceToRoom(neighbor.desV)!.wt
if wt < distances[neighbor.desV] {
distances[neighbor.desV] = wt
previousPaths[neighbor.desV] = closestNode!
}
return nil
})
}
}
return previousPaths
}
// File Management
//let url = NSURL(string:"file:///Users/caleb/Documents/Xcode/CRHS/CRHS/dtb.txt")!
let url = NSURL(string: "file:///Users/caleb/Desktop/rooms.txt")!
let data = NSData(contentsOfURL: url)
let sdata = String(data: data!, encoding: NSUTF8StringEncoding)
let dataArray = sdata!.characters.split{$0 == "\n"}.map(String.init)
var rooms = [String:Room]()
print("data:\n-------")
for i in 0 ..< dataArray.count {
let conn = dataArray[i].characters.split{$0 == "\t"}.map(String.init)
var room1: Room
if ( rooms[conn[0]] == nil ) {
room1 = Room(name: conn[0])
} else {
room1 = rooms[conn[0]]!
}
let room2 = Room(name: conn[2])
let edwt = (conn[1] as NSString).doubleValue
var edge = Edge(wt: edwt, desV: room2)
if room1.neighbors.count == 0 {
room1.neighbors = [ edge ]
} else {
var found: Bool = false
for e in room1.neighbors {
if ( e.desV.name == edge.desV.name ) {
found = true
}
}
if ( found == false ) {
room1.neighbors.append(edge)
}
}
rooms[conn[0]] = room1
}
for (nam,room) in rooms {
print(nam)
print("----")
for n in room.neighbors {
if let un = n.desV.name {
print( un, terminator: " weight: ")
}
print( n.wt )
}
print("\n")
}
var namessofrooms = rooms.map { $0.0 }
var roomsofrooms = rooms.map { $0.1 }
print("Rooms:")
print(rooms)
print("-------\n")
// Calculating
var source = rooms["a1"]!
var target = rooms["a4"]!
roomsofrooms.append(source)
var reversedRooms: Array = roomsofrooms.reverse()
reversedRooms.append(target)
var graph = Graph(vertices: reversedRooms)
var paths = dijkstra(graph, target: target)
var pathVertices:[Room] = [target]
var child = target
while (child != source) {
print(child.name)
print(":::::")
child = paths[child]! //Error occurs here
print(child.name)
pathVertices.append(child)
}
var pathString:[String] = pathVertices.reverse().map { (Room:Room) -> String in
return Room.name!
}
print("solution:\n-------")
print(pathString)
Below is the file I input:
a1 1 a2
a2 1 a3
a3 1 a4
If I input the following file the code above will not work:
a1 1 a2
a2 1 a3
a3 1 a4
a4 1 a5
(Update: File Map Clarification)
First column is the first room, second is the weight between the rooms, third is the room connected to the first.