I try to parse my txt file, i have question and 5 answers in txt file, and i want to save question to separate variable, and similar answers.
My code:
do {
let path = Bundle.main.path(forResource: "data", ofType: "txt")
let source = try? String.init(contentsOfFile: path!)
var elements = source?.components(separatedBy: "\n")
var parsedObject = [[String: String]]()
for i in 0..<(elements?.count)! - 1 {
let objects = [String : String]()
let element = elements![i]
//print(element)
let objectsElement = element.components(separatedBy: "\r")
let question = objectsElement[0]
let answer1 = objectsElement[1]
let answer2 = objectsElement[2]
let answer3 = objectsElement[3]
let anserr4 = objectsElement[4]
let answer5 = objectsElement[5]
print(question, answer1, answer2, answer3, anserr4, answer5)
print(objectsElement)
}
}
Error which i received:
Thread 1: Fatal error: Index out of range
If i comment this code:
let question = objectsElement[0]
let answer1 = objectsElement[1]
let answer2 = objectsElement[2]
let answer3 = objectsElement[3]
let anserr4 = objectsElement[4]
let answer5 = objectsElement[5]
print(question, answer1, answer2, answer3, anserr4, answer5)
I will get such output:
Structure file:
https://drive.google.com/file/d/1ah1Mk_WY3b_qbqKM18nxxPT1rNXFiISa/view?usp=sharing
Parsing a text file is very, very tedious.
Here is a quick&dirty converter from your .txt file to JSON using regular expression.
The JSON dictionaries have the format
question (String), the question text
answers([String]), an array of answers
correctAnswerIndex (Int), the zero-based index of the correct answer in answers
number (Int), the question number.
The regex pattern searches for
one or more digits or one letter in range A...E (is captured) - (\\d+|[A-E])
followed by a dot and a whitespace character - \\.\\s
followed by one or more arbitrary characters (is captured) - (.+)
let url = Bundle.main.url(forResource: "data", withExtension: "txt")!
let string = try! String(contentsOf: url, encoding: .utf8)
let pattern = "(\\d+|[A-E])\\.\\s(.+)"
let regex = try! NSRegularExpression(pattern: pattern)
var result = [[String:Any]]()
var question = [String:Any]()
var answers = [String]()
var answerCounter = 0
let matches = regex.matches(in: string, range: NSRange(string.startIndex..<string.endIndex, in: string))
for match in matches {
let index = string[Range(match.range(at: 1), in: string)!]
var text = string[Range(match.range(at: 2), in: string)!]
if let ordinal = Int(index) {
question["number"] = ordinal
question["question"] = text
} else {
if text.hasPrefix("*") {
text = text.dropFirst()
question["correctAnswerIndex"] = answerCounter
}
answers.append(String(text))
answerCounter += 1
if answerCounter == 5 {
question["answers"] = answers
result.append(question)
question = [:]
answers = []
answerCounter = 0
}
}
}
let jsonData = try! JSONSerialization.data(withJSONObject: result)
let jsonString = String(data: jsonData, encoding: .utf8)!
print(jsonString)
Save that JSON in the bundle as data.json and delete the text file
Now you can decode the JSON into a struct with
struct Question : Decodable {
let question : String
let answers : [String]
let correctAnswerIndex : Int
let number : Int
}
let url = Bundle.main.url(forResource: "data", withExtension: "json")!
let data = try! Data(contentsOf: url)
do {
let result = try JSONDecoder().decode([Question].self, from: data)
print(result)
} catch {
print(error)
}
Have a look a your txt file by printing source. You'll get something like this:
Optional("1. A patient with ischemic heart disease has been administered inosine which is an intermediate metabolite in the synthesis of:\n A. Metalloproteins\n B. Glycoproteins\n C. Ketone bodies\n D. *Purine nucleotides\n E. Lipoproteins\n\n2. Rates of chemical reactions of the same order are compared by:\n A. *Constant of chemical reaction rate\n B. Endpoint of a reaction\n C. Change in the concentration of the reaction products\n D. Change in the reactants concentration\n E. Chemical reaction rate\n\n ...etc)
As you can see, Questions are separated by \n\n
var elements = source?.components(separatedBy: "\n\n")
Then you can separate the question and its answers by \n
let objectsElement = element.components(separatedBy: "\n")
Make sure in your txt file that Questions are separated by two newlines, and that question have only one new line between them.
In your case one way to achieve what you want is this:
do {
let path = Bundle.main.path(forResource: "Data", ofType: "txt")
let source = try? String.init(contentsOfFile: path!)
var elements = source?.components(separatedBy: "\n\n") // Here is the change
var parsedObject = [[String: String]]()
for i in 0..<(elements?.count)! - 1 {
let objects = [String : String]()
let element = elements![i]
let objectsElement = element.components(separatedBy: "\n") // And Here
if objectsElement.count > 1 {
let question = objectsElement[0]
let answer1 = objectsElement[1]
let answer2 = objectsElement[2]
let answer3 = objectsElement[3]
let answer4 = objectsElement[4]
let answer5 = objectsElement[5]
print("Question: \(question)")
print("Answer: \(answer1)")
print("Answer: \(answer2)")
print("Answer: \(answer3)")
print("Answer: \(answer4)")
print("Answer: \(answer5)")
}
}
}
If you are not changing your text file format then in this way you can get the solution. For now you can try this solution but search for better one or if I will get one I will post the answer soon.
You are trying to access elements in the location 0 to 5 of the array objectsElement. So whenever the objectsElement.count < 6 its going to throw Thread 1: Fatal error: Index out of range
Seeing your output i think every time objectsElement has only one element in it.
Related
In this I am getting data from server response after posting parameters and here I need to display it on table view and it should be displayed like shown below in the image 0 is the price for the particular shipping method
already i had written model class for server response data and here it is
struct ShippingMethod {
let carrierCode : String
let priceInclTax : Int
let priceExclTax : Int
let available : Any
let carrierTitle : String
let baseAmount : Int
let methodTitle : String
let amount : Int
let methodCode : String
let errorMessage : Any
init(dict : [String:Any]) {
self.carrierCode = dict["carrier_code"] as! String
self.priceInclTax = dict["price_incl_tax"]! as! Int
self.priceExclTax = dict["price_excl_tax"]! as! Int
self.available = dict["available"]!
self.carrierTitle = dict["carrier_title"] as! String
self.baseAmount = dict["base_amount"]! as! Int
self.methodTitle = dict["method_title"]! as! String
self.amount = dict["amount"]! as! Int
self.methodCode = dict["method_code"] as! String
self.errorMessage = (dict["error_message"] != nil)
}
}
by using this I had formed an array type like this by using code
var finalDict = [String: [String]]()
var responseData = [ShippingMethod]()
do
{
let array = try JSONSerialization.jsonObject(with: data, options: []) as? [[String : Any]]
for item in array! {
self.responseData.append(ShippingMethod.init(dict: item))
}
print(self.responseData)
}
catch let error
{
print("json error:", error)
}
print(self.responseData)
for item in self.responseData {
let dict = item
let carrierTitle = dict.carrierTitle
let methodTitle = dict.methodTitle
if self.finalDict[carrierTitle] == nil {
self.finalDict[carrierTitle] = [String]()
}
self.finalDict[carrierTitle]!.append(methodTitle)
}
print(self.finalDict)
the output of this finalDict is ["Flat Rate": ["Fixed"], "Best Way": ["Table Rate"]] in this carrier title key value pair should be displayed as section title and is Flat Rate and method title key value pair should be displayed as rows in section Fixed but the problem is I need amount key value pair with it also for corresponding method title can anyone help me how to get this ?
Why don't you create another struct for displaying row data:
struct CarrierInfo {
let name:String
let amount:Int
}
Change your finalDict to
var finalDict = [String: [CarrierInfo]]()
and create CarrierInfo instance and set it in finalDict
for item in self.responseData {
let dict = item
let carrierTitle = dict.carrierTitle
let methodTitle = dict.methodTitle
let amount = dict.amount
if self.finalDict[carrierTitle] == nil {
self.finalDict[carrierTitle] = [CarrierInfo]()
}
self.finalDict[carrierTitle]!.append(CarrierInfo(name: carrierTitle, amount: amount))
}
Likewise you can make other required changes. This would neatly wrap your row display data inside a structure.
PS: I have not tested the code in IDE so it may contain typos.
You can assign another dictionary with key as methodTitle and amount as value. i.e., ["fixed":"whatever_amount"]
OR
You can use finalDict differently, like ["Flat Rate": ["tilte":"Fixed","amount":"0"], "Best Way": ["title":"Table Rate","amount":"0"]]
If it is difficult for you to code this, you can revert back.
Edit
You can use the following code to create the array in the second solution I suggested above:
for item in self.responseData {
let dict = item
let carrierTitle = dict.carrierTitle
let methodTitle = dict.methodTitle
let amount = dict.amount
if self.finalDict[carrierTitle] == nil {
self.finalDict[carrierTitle] = [[String:String]]()
}
let innerDict = ["title":methodTitle,"amount":amount]
self.finalDict[carrierTitle]!.append(innerDict)
}
Hello i have variables but gives all of them Optional(). How can i resolve them my codes under below.
Json append codes for koltuklar koltuklaridler array under below you can see
for name in json as! [AnyObject] {
let SeatName = name["SeatName"]
let SeatDesignId = name["SeatDesignId"]
self.koltuklar.append("\(SeatName)*\(SeatDesignId)*")
if let blogs = json["SeatDetail"] as? [[String: AnyObject]] {
for blog in blogs {
let TicketTypeId = blog["TicketTypeId"]
let TicketTypeName = blog["TicketTypeName"]
let Amount = blog["Amount"]
self.koltuklaridler.append("\(SeatDesignId)*\(TicketTypeId)*\(TicketTypeName)*\(Amount)*")
}
}
Under below you can see tableview inside codes ( That codes doing open koltuklar index path item after search id inside koltuklaridler and when found take some varibles from it )
var koltuklar = [""]
var koltuklaridler = [""]
if let myStrings:String! = koltuklar[indexPath.row]{
print("\(myStrings!)")
let myStringArrf = myStrings.componentsSeparatedByString("*")
print("\(myStringArrf)")
if let koltukisims:String! = String(myStringArrf[0]) {
cell.koltukName.text = koltukisims
}
print(" STR - \(myStringArrf[1] as String!)")
if let index = koltuklaridler.indexOf(myStringArrf[1] as String!) {
let myStringdetaysecilen = koltuklaridler[index]
print("myStringdetaysecilen \(myStringdetaysecilen)")
}
Also my json file
[
{
"SeatDesignId": 16484,
"SeatName": "A6",
"SaloonId": 148,
"SeatDetail": [
{
"TicketTypeId": 1,
"TicketTypeName": "Okay",
"Amount": 13
}
]
},
Output
Optional("A17")*Optional(16254)*
["Optional(\"A17\")", "Optional(16254)", ""]
STR - Optional(16254)
All variables output Optional i try everything but doesn't fix.
As mentioned in my comments, whenever you use String Interpolation "\(...)" make sure that all optional strings are unwrapped. Values read from dictionaries are always optional.
This code unwraps all optional strings
for name in json as! [[String:AnyObject]] {
guard let SeatName = name["SeatName"] as? String,
SeatDesignId = name["SeatDesignId"] as? Int else {
continue
}
self.koltuklar.append("\(SeatName)*\(SeatDesignId)*")
if let blogs = json["SeatDetail"] as? [[String: AnyObject]] {
for blog in blogs {
if let TicketTypeId = blog["TicketTypeId"] as? Int,
TicketTypeName = blog["TicketTypeName"] as? String,
Amount = blog["Amount"] as? Int {
self.koltuklaridler.append("\(SeatDesignId)*\(TicketTypeId)*\(TicketTypeName)*\(Amount)*")
}
}
}
Edit: I updated the casting to the actual types according to the JSON
Now declare both arrays as empty string arrays.
var koltuklar = [String]()
var koltuklaridler = [String]()
and remove the optional binding in the first line
let myStrings = koltuklar[indexPath.row]
print("\(myStrings)")
...
By the way: Your way to "serialize" the strings with asterisks and deserialize it in the table view is very, very clumsy and inefficient. Use a custom class or struct for the data records.
Your problem is that you are creating a string from values from dict without a if let statement so it returns an optional value:
for name in json as! [AnyObject] {
if let SeatName = name["SeatName"],
let SeatDesignId = name["SeatDesignId"] {
self.koltuklar.append("\(SeatName)*\(SeatDesignId)*")
}
if let blogs = json["SeatDetail"] as? [[String: AnyObject]] {
for blog in blogs {
if let TicketTypeId = blog["TicketTypeId"],
let TicketTypeName = blog["TicketTypeName"],
let Amount = blog["Amount"] {
self.koltuklaridler.append("\(SeatDesignId)*\(TicketTypeId)*\(TicketTypeName)*\(Amount)*")
}
}
}
There is a two way of operate optional.
unwrapped using "!" but in this chances of crash if value is nil.
unwrapped using term call "optional binding" using "if let" condition.
if let var = "assigned your optional variable"{
print(var)
}
You will get your variable without optional.
My code worked perfectly in the version of Swift in May 2015, when I programmed the app. When I opened XCode 7.2 today I get an odd error message I can't understand: Ambiguous use of 'subscript'. In total I get this error 16 times in my code, do anyone know what I can change to fix this problem?
if let path = NSBundle.mainBundle().pathForResource("colorsAndAlternatives", ofType: "plist") {
if let dict = NSMutableDictionary(contentsOfFile: path) {
let randomNumber = Int(arc4random_uniform(UInt32(numberOfOptions)))
let correctColor = "#" + String(dict["\(randomNumber)"]![1] as! Int!, radix: 16, uppercase: true) // Ambiguous use of 'subscript'
The correctColor is determined by HEX using this code: https://github.com/yeahdongcn/UIColor-Hex-Swift/blob/master/HEXColor/UIColorExtension.swift
The Swift compiler is much more strict now.
Here, it doesn't know for sure what type is the result of dict["\(randomNumber)"] so it bails and asks for precisions.
Help the compiler understand that the result is an array of Ints and that you can access it alright with subscript, for example:
if let result = dict["\(randomNumber)"] as? [Int] {
let correctColor = "#" + String(result[1], radix: 16, uppercase: true)
}
Here's my attempt to unwrap what's going on:
if let path = NSBundle.mainBundle().pathForResource("colorsAndAlternatives", ofType: "plist") {
if let dict = NSMutableDictionary(contentsOfFile: path) {
let randomNumber = Int(arc4random_uniform(UInt32(numberOfOptions)))
let numberKey = "\(randomNumber)"
if let val = dict[numberKey] as? [AnyObject] { // You need to specify that this is an array
if let num = val[1] as? NSNumber { // If your numbers are coming from a file, this is most likely an array of NSNumber objects, since value types cannot be stored in an NSDictionary
let str = String(num.intValue, radix: 16, uppercase: true) // construct a string with the intValue of the NSNumber
let correctColor = "#" + str
}
}
}
}
I try to parse JSON using SwiftyJSON but I face a problem.
I use tutorial from here
Here is my code
var JSONStorage : [Article?]?
var objects = [[String: String]]()
override func viewDidLoad() {
super.viewDidLoad()
let number = arc4random_uniform(1000)
let urlString = "http://78.27.190.58:3200/api/get_article/17?\(number)"
if let url = NSURL(string: urlString) {
if let data = try? NSData(contentsOfURL: url, options: []) {
let json = JSON(data: data)
for element in json["article"].arrayValue {
let id = Int(element["id"].stringValue)
let title = element["title"].stringValue
let subtitle = element["subtitle"].stringValue
let body = element["body"].stringValue
let img = element["images"]["main"].rawValue
let obj = ["id": id, "title": title, "subtitle": subtitle, "body": body, "img": img]
objects.append(obj)
}
}
}
}
here is the error I got
What am I doing wrong ?
Your objects is of type [[String:String]], an array of dictionaries. These dictionaries are typed String for the key and String for the value.
But you add to this array a dictionary containing different types of values: Strings with your .stringValue objects, and a .rawValue one which must be of type NSData I presume.
A solution could be to type your dictionary [String:AnyObject] (and thus your array [[String:AnyObject]]) and to typecast on the other side when retrieving the values.
By the way, the compiler says "expression is ambiguous" because it failed at inferring the type of the array (and now we know why).
Note that it's not the most efficient when parsing JSON. A better way would be to create objects from your data: classes or structs. You could have a struct Article to hold each element values for example, like you showed in another question.
Arrays or Dictionaries in swift can not be heterogeneous. If you look at your code you want to put one int and four strings as values of obj.
This will work:
for element in json["article"].arrayValue {
let id = element["id"].stringValue // must be string to put it in obj
let title = element["title"].stringValue
let subtitle = element["subtitle"].stringValue
let body = element["body"].stringValue
let img = element["images"]["main"].rawValue
let obj: [String: String] = ["id": id, "title": title, "subtitle": subtitle, "body": body, "img": img]
objects.append(obj)
}
I have around 150 images that I am downloading and uploading through a for loop. I am also naming each one. I have named them according the whatever my i value is at the moment that the image is uploaded, but that only gives me the exact digits of the i value. I need three digits for every name.
For example, I need 001.png, 002.png,... 123.png, 124.png... etc. But instead, I get 1.png, 2.png... 123.png, 124.png... etc.
My code is here:
var n = 4
func addCards(urlString:String, numberString:String) {
let url = NSURL(string: urlString)
let urlRequest = NSURLRequest(URL: url!)
NSURLConnection.sendAsynchronousRequest(urlRequest, queue: NSOperationQueue.mainQueue(), completionHandler: {
response, data, error in
var image = UIImage(data: data)
let imageData = UIImagePNGRepresentation(image)
let imageFile = PFFile(name: "XY\(n)_\(numberString)", data:imageData)
var cardUpload = PFObject(className:"Cards")
cardUpload["Image"] = imageFile
cardUpload["Expansion"] = "XY\(n)"
cardUpload["Number"] = "XY_\(n)_\(numberString)"
//THIS (Number) IS THE LINE THAT NAMES MY FILES
cardUpload.save()
})
}
for var i = 1; i < 151; i++ {
addCards("http://image.com/image\(i)", "\(i)")
}
You need to format your string to have 3 digits instead of just using the index (which is an unformatted Int) so:
String(format: "%03d.png", i)
Use the format string "%03d" to get numbers with three digits and leading zeros.
for myInt in 1...150 {
println(String(format: "%03d", myInt))
}
I'm a big fan of using Cocoa's formatter classes. In your case, I'd use NSNumberFormatter, which is great for forming all kinds of strings from numbers. Try this code out -- it outputs a list of filenames, from 001.png to 150.png:
let threeDigitFormatter = NSNumberFormatter()
threeDigitFormatter.minimumIntegerDigits = 3
for imageNumber in 1...150 {
let formattedImageNumber = threeDigitFormatter.stringFromNumber(imageNumber)!
let filename = "\(formattedImageNumber).png"
println(filename)
}