I'm parsing a text file to get the latitude and longitude of locations. I need to convert the lon/lat strings to doubles, but I can't do it.
I've tried both the Double(String) method and the (String as NSNumber).doubleValue. It always gives nil.
When I type in the numbers manually it works.
Here's the code snippet:
var items = [[String]]()
func readParkingData() {
guard let filepath = Bundle.main.path(forResource: "parking", ofType: "txt") else {
print("file not found")
return
}
print("file path : \(filepath)")
do{
let content = try String(contentsOfFile: filepath, encoding: .utf8)
let attributed = content.htmlAttributedString
let decoded : String = attributed!.string
let split = decoded.split(separator: ";")
var count = 0
var item = [String]()
for word in split {
item.append(String(word))
count += 1
if count == 30 {
items.append(item)
item = [String]()
count = 0
}
}
for entry in items {
print(entry[24])
print(entry[25])
let latString : String = entry[24]
let lonString : String = entry[25]
print(type(of: latString))
let lat = Double(latString)
print(lat)
}
}catch{
print("file read error \(filepath)")
}
}
I've looked through the other answers. The type of latString is String, not optional. Trimming white spaces didn't help either. lat is always nil.
What's going on here?
Apparently the floating point numbers are enclosed in quotation marks,
so you'll need not only trim whitespace but also quotation marks. Example:
let latString = "\"12.34\""
print(latString) // "12.34"
var cs = CharacterSet.whitespaces
cs.insert("\"")
let trimmedLatString = latString.trimmingCharacters(in: cs)
print(trimmedLatString) // 12.34
print(Double(trimmedLatString)!) // 12.34
Further remarks:
I do not see the reason to operate on the htmlAttributedString, you
probably should split the original content into lines and fields.
Is your input a CSV-formatted file? There are open source CSV reader libraries
which you might try.
Related
I am reading in a text file of translation pairs of this format:
boy:garçon
garçon:boy
Into an array using the following code:
var vocab:[String:String] = [:]
let path = Bundle.main.path(forResource: "words_alpha", ofType: "txt")!
let text = try! String(contentsOfFile: path, encoding: String.Encoding.utf8)
let vocab = text.components(separatedBy: CharacterSet.newlines)
The imported array looks like this:
["boy:garçon", "garçon:boy"]
Whereas I would like the array to be formatted like this:
["boy":"garçon", "garçon":"boy"]
What is the best way to achieve the desired array format shown above using a Swift string transformation?
Have been trying to use .split, but with not much success.
Let's be clear:
["boy":"garçon", "garçon":"boy"]
That's a Dictionary, not an Array.
There a multiples ways to do that, here's two possible codes:
var manual: [String: String] = [:]
array.forEach { aString in
let components = aString.components(separatedBy: ":")
guard components.count == 2 else { return }
manual[components[0]] = components[1]
}
print(manual)
or
let reduced = array.reduce(into: [String: String]()) { result, current in
let components = current.components(separatedBy: ":")
guard components.count == 2 else { return }
result[components[0]] = components[1]
}
print(reduced)
Output (for both):
$> ["garçon": "boy", "boy": "garçon"]
As said, it's a Dictionary, so there is no guarantee that the print be:
["garçon": "boy", "boy": "garçon"] or ["boy":"garçon", "garçon":"boy"], it's key-value access, not index-value access.
I was wondering what is the best way to take a text file containing lines in this format:
Last_name:First_name:Number_of_cats:Number_of_dogs:Number_of_fish:Number_of_other_pets
and produce a text file containing lines in this format:
First_name Last_name:Total_number_of_pets
For example, it would take a text file containing this:
Apple:Tim:0:0:3:0
Jobs:Steve:0:0:5:2
Da Kid:Billie:0:1:0:1
White:Walter:2:1:1:0
Bond:James:2:2:3:0
Stark:Tony:0:1:2:0
Wayne:Bruce:0:0:0:0
and output a text file that would look like this:
Tim Apple:3
Steve Jobs:7
Bille Da Kid:2
Walter White:4
James Bond:7
Tony Stark:3
Bruce Wayne:0
Here's what I've been trying so far without any success:
let namesFile = "Names"
let dir = try? FileManager.default.url(for: .documentDirectory,
in: .userDomainMask, appropriateFor: nil, create: true)
// If the directory was found, we write a file to it and read it back
if let fileURL = dir?.appendingPathComponent(namesFile).appendingPathExtension("txt") {
print (fileURL)
var petSum = 0;
do {
let entriesString = try String(contentsOf: fileURL)
let entries = entriesString.components(separatedBy: "\n")
for entry in entries {
let namePets = entry.components(separatedBy: ":")
if (namePets.indices.contains(1)) {
var sum = 0;
print(namePets[1])
print(namePets[0])
for namePet in namePets {
if let intArg = Int(namePet) {
sum = sum + intArg
}
}
print (sum)
print ("\n")
}
}
} catch {
print("Failed reading from URL, Error: " + error.localizedDescription)
}
}
else {
print("didn't work")
}
/*
// Read from the file
var inString = ""
// Write to the file named Test
let outString = "Write this text to the file"
do {
try outString.write(to: fileURL, atomically: true, encoding: .utf8)
} catch {
print("Failed writing to URL: \(fileURL), Error: " + error.localizedDescription)
}
*/
Would appreciate any help, thank you!
Split the text into lines.
Map each line
Split the line into components separated by colon.
Convert field 2 through end to Int and sum them up.
Return the string in the new format.
Join the result.
let text = """
Apple:Tim:0:0:3:0
Jobs:Steve:0:0:5:2
Da Kid:Billie:0:1:0:1
White:Walter:2:1:1:0
Bond:James:2:2:3:0
Stark:Tony:0:1:2:0
Wayne:Bruce:0:0:0:0
"""
let lines = text.components(separatedBy: .newlines)
let convertedLines = lines.map { line in
let components = line.components(separatedBy: ":")
return "\(components[1]) \(components[0]):\(components[2...].compactMap(Int.init).reduce(0, +))"
}
let result = convertedLines.joined(separator: "\n")
I have this string: "lat/lng: (-6.2222391,106.7035684)"
I only need to get those double data type in that string. so how to get just only *-6.222239*1 and 106.7035684 as string variable?
How to get the number in that parenthesis?
So I think I have get string after "(" and before "," to get "-6.2222391" and also after "," and before ")" to get "106.7035684"
but I don't know how to get that in code
let source = "lat/lng: (-6.2222391,106.7035684)"
let splited = source.components(separatedBy: "lat/lng: ")[1] //separating
let removed = splited.replacingOccurrences(of: "(", with: "").replacingOccurrences(of: ")", with: "") // removing
let coord = removed.components(separatedBy: ",") // removing
let lat = Double(coord[0])
let lng = Double(coord[1])
You could use a regex:
let str = "lat/lng: (-6.2222391,106.7035684)"
let rg = NSRange(location: 0, length: (str as NSString).length)
let latRegex = try! NSRegularExpression(pattern: "(?<=\\()[+-\\.0-9]+(?=,)")
(?<=\\() is positive lookbehind, it looks for anything preceded by (,
[+-\\.0-9]+ eagerly looks for at least one character or more that are either +, -, ., or a digit from 0 to 9,
(?=,) is positive lookahead, it matches anything followed by ,.
Now let's use this regular expression :
let latitude: Double? = latRegex.matches(in: str, range: rg)
.compactMap { Double(str[Range($0.range, in: str)!]) }
.first
if let lat = latitude {
print(lat) //-6.2222391
}
In the same way, we can get the longitude :
let longRegex = try! NSRegularExpression(pattern: "(?<=,)[+-\\.0-9]+(?=\\))")
let longitude: Double? = longRegex.matches(in: str, range: rg)
.compactMap { Double(str[Range($0.range, in: str)!]) }
.first
if let long = longitude {
print(long) //106.7035684
}
PS: I've used forced unwrapping here and there for brevity
You have received some good answers already. But I think I've come up with a more compact version.
You need to care about - 0~9 . and ,
The , will be considered only for having the two components and then be used for separating them.
See this:
let source = "lat/lng: (-6.2222391,106.7035684)"
let allowedCharactersString = "-01234567890.,"
let latLongValues = String(source.characters.filter {
allowedCharactersString.characters.contains($0)
}).components(separatedBy: ",")
print(latLongValues.first!) // "-6.2222391"
print(latLongValues.last!) // "106.7035684"
Try this:
let source = "lat/lng: (-6.2222391,106.7035684)".components(separatedBy: ")")[0]
let removed = source.components(separatedBy: "(")[1];// removing
//OR
//let source = "lat/lng: (-6.2222391,106.7035684)".components(separatedBy: "(")[1]
//let removed = source.components(separatedBy: ")")[0];// removing
let coord = removed.components(separatedBy: ",") // removing
let lat = Double(coord[0])
let lng = Double(coord[1])
I am getting this below string as a response from WS API when they send emoji as a string:
let strTemp = "Hii \\xF0\\x9F\\x98\\x81"
I want it to be converted to the emoji icon like this -> Hii 😁
I think so it is coming in UTF-8 Format as explained in the below Image: Image Unicode
I have tried decoding it Online using UTF-8 Decoder
And i got the emoticon Successfully decoded
Before Decoding:
After Decoding:
But the issue here is I do not know how to work with it in Swift.
I referred following link but it did not worked for me.
Swift Encode/decode emojis
Any help would be appreciated.
Thanks.
As you already given the link of converter tool which is clearly doing UTF-8 encoding and decoding. You have UTF-8 encoded string so here is an example of UTF8-Decoding.
Objective-C
const char *ch = [#"Hii \xF0\x9F\x98\x81" cStringUsingEncoding:NSUTF8StringEncoding];
NSString *decode_string = [NSString stringWithUTF8String:ch];
NSLog(#"%#",decode_string);
Output: Hii 😁
Swift
I'm able to convert \\xF0\\x9F\\x98\\x81 to 😁 in SWift.
First I converted the hexa string into Data and then back to String using UTF-8 encoding.
var str = "\\xF0\\x9F\\x98\\x81"
if let data = data(fromHexaStr: str) {
print(String(data: data, encoding: String.Encoding.utf8) ?? "")
}
Output: 😁
Below is the function I used to convert the hexa string into data. I followed this answer.
func data(fromHexaStr hexaStr: String) -> Data? {
var data = Data(capacity: hexaStr.characters.count / 2)
let regex = try! NSRegularExpression(pattern: "[0-9a-f]{1,2}", options: .caseInsensitive)
regex.enumerateMatches(in: hexaStr, range: NSMakeRange(0, hexaStr.utf16.count)) { match, flags, stop in
let byteString = (hexaStr as NSString).substring(with: match!.range)
var num = UInt8(byteString, radix: 16)!
data.append(&num, count: 1)
}
guard data.count > 0 else { return nil }
return data
}
Note: Problem with above code is it converts hexa string only not combined strings.
FINAL WORKING SOLUTION: SWIFT
I have done this by using for loop instead of [0-9a-f]{1,2} regex because this will also scan 81, 9F, Any Two digits number which is wrong obviously.
For example: I have 81 INR \\xF0\\x9F\\x98\\x81.
/// This line will convert "F0" into hexa bytes
let byte = UInt8("F0", radix: 16)
I made a String extension in which I check upto every 4 characters if it has prefix \x and count 4 and last two characters are convertible into hexa bytes by using radix as mentioned above.
extension String {
func hexaDecoededString() -> String {
var newData = Data()
var emojiStr: String = ""
for char in self.characters {
let str = String(char)
if str == "\\" || str.lowercased() == "x" {
emojiStr.append(str)
}
else if emojiStr.hasPrefix("\\x") || emojiStr.hasPrefix("\\X") {
emojiStr.append(str)
if emojiStr.count == 4 {
/// It can be a hexa value
let value = emojiStr.replacingOccurrences(of: "\\x", with: "")
if let byte = UInt8(value, radix: 16) {
newData.append(byte)
}
else {
newData.append(emojiStr.data(using: .utf8)!)
}
/// Reset emojiStr
emojiStr = ""
}
}
else {
/// Append the data as it is
newData.append(str.data(using: .utf8)!)
}
}
let decodedString = String(data: newData, encoding: String.Encoding.utf8)
return decodedString ?? ""
}
}
USAGE:
var hexaStr = "Hi \\xF0\\x9F\\x98\\x81 81"
print(hexaStr.hexaDecoededString())
Hi 😁 81
hexaStr = "Welcome to SP19!\\xF0\\x9f\\x98\\x81"
print(hexaStr.hexaDecoededString())
Welcome to SP19!😁
I fix your issue but it need more work to make it general , the problem here is that your Emijo is Represented by Hex Byte x9F , so we have to convert this Hex to utf8 then convert it to Data and at last convert data to String
Final result Hii 😁 Please read comment
let strTemp = "Hii \\xF0\\x9F\\x98\\x81"
let regex = try! NSRegularExpression(pattern: "[0-9a-f]{1,2}", options: .caseInsensitive)
// get all matched hex xF0 , x9f,..etc
let matches = regex.matches(in: strTemp, options: [], range: NSMakeRange(0, strTemp.count))
// Data that will hanlde convert hex to UTf8
var emijoData = Data(capacity: strTemp.count / 2)
matches.enumerated().forEach { (offset , check) in
let byteString = (strTemp as NSString).substring(with: check.range)
var num = UInt8(byteString, radix: 16)!
emijoData.append(&num, count: 1)
}
let subStringEmijo = String.init(data: emijoData, encoding: String.Encoding.utf8)!
//now we have your emijo text 😁 we can replace by its code from string using matched ranges `first` and `last`
// All range range of \\xF0\\x9F\\x98\\x81 in "Hii \\xF0\\x9F\\x98\\x81" to replce by your emijo
if let start = matches.first?.range.location, let end = matches.last?.range.location , let endLength = matches.last?.range.length {
let startLocation = start - 2
let length = end - startLocation + endLength
let sub = (strTemp as NSString).substring(with: NSRange.init(location: startLocation, length: length))
print( strTemp.replacingOccurrences(of: sub, with: subStringEmijo))
// Hii 😁
}
I'm trying to read in a file from disk and parse its data into a nice format. However, the function is not returning anything. It returns an empty array. Why is this?
Note: I've been tinkering around with this and I've managed to simplify the previous 2 functions to just this one.
The function:
func openFile(_ fileName:String, _ fileType:String) -> [(Double, Double)] {
let file = fileName + fileType //this is the file. we will write to and read from it
var text2: String = ""
if let dir = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first {
let path = dir.appendingPathComponent(file)
//reading
do {
text2 = try String(contentsOf: path, encoding: String.Encoding.utf8)
}
catch {/* error handling here */}
}
var pairs = [(Double,Double)]()
var words: [String] = []
for line in (text2.components(separatedBy: "\n").dropFirst()){
if line != "" {
words = line.components(separatedBy: "\t")
pairs.append((Double(words[0])!,Double(words[1])!))
}
}
return pairs
}