Swift Converting Format in Text File (String Parsing) - swift

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")

Related

convert string to double gives nil

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.

How to read a specific file's line in swift4?

Testing in Playground I read a whole file in an array of String, one string per line.
But what I need is a specific line only:
let dir = try? FileManager.default.url(for: .documentDirectory,
in: .userDomainMask, appropriateFor: nil, create: true)
let fileURL = dir!.appendingPathComponent("test").appendingPathExtension("txt")
let text: [String] = try String(contentsOf: fileURL).components(separatedBy: NSCharacterSet.newlines)
let i = 2 // computed before, here to simplify
print(text[i])
There is a way to avoid reading the complete big file?
I'm guessing you mean that you want to retrieve the index without manually searching the array with, say, a for-in loop.
In Swift 4 you can use Array.index(where:) in combination with the StringProtocol's generic contains(_:) function to find what you're looking for.
Let's imagine you're looking for the first line containing the text "important stuff" in your text: [String] array.
You could use:
text.index(where: { $0.contains("important stuff") })
Behind the scenes, Swift is looping to find the text, but with built-in enhancements, this should perform better than manually looping through the text array.
Note that the result of this search could be nil if no matching lines are present. Therefore, you'll need to ensure it's not nil before using the result:
Force unwrap the result (risking the dreaded fatal error: unexpectedly found nil while unwrapping an Optional value):
print(text[lineIndex!)
Or, use an if let statement:
if let lineIndex = stringArray.index(where: { $0.contains("important stuff") }) {
print(text[lineIndex])
}
else {
print("Sorry; didn't find any 'important stuff' in the array.")
}
Or, use a guard statement:
guard let lineIndex = text.index(where: {$0.contains("important stuff")}) else {
print("Sorry; didn't find any 'important stuff' in the array.")
return
}
print(text[lineIndex])
To find a specific line without reading the entire file in, you could use this StreamReader answer. It contains code that worked in Swift 3. I tested it in Swift 4, as well: see my GitHub repo, TEST-StreamReader, for my test code.
You would still have to loop to get to the right line, but then break the loop once you've retrieved that line.
Here's the StreamReader class from that SO answer:
class StreamReader {
let encoding : String.Encoding
let chunkSize : Int
var fileHandle : FileHandle!
let delimData : Data
var buffer : Data
var atEof : Bool
init?(path: String, delimiter: String = "\n", encoding: String.Encoding = .utf8,
chunkSize: Int = 4096) {
guard let fileHandle = FileHandle(forReadingAtPath: path),
let delimData = delimiter.data(using: encoding) else {
return nil
}
self.encoding = encoding
self.chunkSize = chunkSize
self.fileHandle = fileHandle
self.delimData = delimData
self.buffer = Data(capacity: chunkSize)
self.atEof = false
}
deinit {
self.close()
}
/// Return next line, or nil on EOF.
func nextLine() -> String? {
precondition(fileHandle != nil, "Attempt to read from closed file")
// Read data chunks from file until a line delimiter is found:
while !atEof {
if let range = buffer.range(of: delimData) {
// Convert complete line (excluding the delimiter) to a string:
let line = String(data: buffer.subdata(in: 0..<range.lowerBound), encoding: encoding)
// Remove line (and the delimiter) from the buffer:
buffer.removeSubrange(0..<range.upperBound)
return line
}
let tmpData = fileHandle.readData(ofLength: chunkSize)
if tmpData.count > 0 {
buffer.append(tmpData)
} else {
// EOF or read error.
atEof = true
if buffer.count > 0 {
// Buffer contains last line in file (not terminated by delimiter).
let line = String(data: buffer as Data, encoding: encoding)
buffer.count = 0
return line
}
}
}
return nil
}
/// Start reading from the beginning of file.
func rewind() -> Void {
fileHandle.seek(toFileOffset: 0)
buffer.count = 0
atEof = false
}
/// Close the underlying file. No reading must be done after calling this method.
func close() -> Void {
fileHandle?.closeFile()
fileHandle = nil
}
}
extension StreamReader : Sequence {
func makeIterator() -> AnyIterator<String> {
return AnyIterator {
return self.nextLine()
}
}
}

How can I bring in data from a text file on disk without adding it to the project?

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
}

Reading from txt file in Swift 3

I want to know how to read from a txt file and print out specific parts of the file?
For example, "test.txt" will contain:
'''Jason 16 male self programing
Josh 15 male friend art'''
So I am looking for a way to print each word and line separately. Such as only printing:
"Jason"
"Jason is 16"
"Josh likes art"
This is what I got so far from searching around
if let filepath = Bundle.main.path(forResource: "test", ofType: "txt")
{
do
{
let contents = try String(contentsOfFile: filepath)
print(contents[0])
}
catch
{
// contents could not be loaded
}
}
else
{
// example.txt not found!
}
Thank you for your support.
Once you have read your file into contents you can break it into lines and words with code like:
let lines = contents.components(separatedBy: "\n")
for line in lines {
let words = line.components(separatedBy: " ")
print("\(words[0]) is \(words[1]) and likes \(words[4])")
}

swift: how can I delete a specific character?

a string such as ! !! yuahl! ! , I want delete ! and , when I code like this
for index in InputName.characters.indices {
if String(InputName[index]) == "" || InputName.substringToIndex(index) == "!" {
InputName.removeAtIndex(index)
}
}
have this error " fatal error: subscript: subRange extends past String end ", how should I do? THX :D
Swift 5+
let myString = "aaaaaaaabbbb"
let replaced = myString.replacingOccurrences(of: "bbbb", with: "") // "aaaaaaaa"
If you need to remove characters only on both ends, you can use stringByTrimmingCharactersInSet(_:)
let delCharSet = NSCharacterSet(charactersInString: "! ")
let s1 = "! aString! !"
let s1Del = s1.stringByTrimmingCharactersInSet(delCharSet)
print(s1Del) //->aString
let s2 = "! anotherString !! aString! !"
let s2Del = s2.stringByTrimmingCharactersInSet(delCharSet)
print(s2Del) //->anotherString !! aString
If you need to remove characters also in the middle, "reconstruct from the filtered output" would be a little bit more efficient than repeating single character removal.
var tempUSView = String.UnicodeScalarView()
tempUSView.appendContentsOf(s2.unicodeScalars.lazy.filter{!delCharSet.longCharacterIsMember($0.value)})
let s2DelAll = String(tempUSView)
print(s2DelAll) //->anotherStringaString
If you don't mind generating many intermediate Strings and Arrays, this single liner can generate the expected output:
let s2DelAll2 = s2.componentsSeparatedByCharactersInSet(delCharSet).joinWithSeparator("")
print(s2DelAll2) //->anotherStringaString
I find that the filter method is a good way to go for this sort of thing:
let unfiltered = "! !! yuahl! !"
// Array of Characters to remove
let removal: [Character] = ["!"," "]
// turn the string into an Array
let unfilteredCharacters = unfiltered.characters
// return an Array without the removal Characters
let filteredCharacters = unfilteredCharacters.filter { !removal.contains($0) }
// build a String with the filtered Array
let filtered = String(filteredCharacters)
print(filtered) // => "yeah"
// combined to a single line
print(String(unfiltered.characters.filter { !removal.contains($0) })) // => "yuahl"
Swift 3
In Swift 3, the syntax is a bit nicer. As a result of the Great Swiftification of the old APIs, the factory method is now called trimmingCharacters(in:). Also, you can construct the CharacterSet as a Set of single-character Strings:
let string = "! !! yuahl! !"
string.trimmingCharacters(in: [" ", "!"]) // "yuahl"
If you have characters in the middle of the string you would like to remove as well, you can use components(separatedBy:).joined():
let string = "! !! yu !ahl! !"
string.components(separatedBy: ["!", " "]).joined() // "yuahl"
H/T #OOPer for the Swift 2 version
func trimLast(character chars: Set<Character>) -> String {
let str: String = String(self.reversed())
guard let index = str.index(where: {!chars.contains($0)}) else {
return self
}
return String((str[index..<str.endIndex]).reversed())
}
Note:
By adding this function in String extension, you can delete the specific character of string at last.
for index in InputName.characters.indices.reversed() {
if String(InputName[index]) == "" || InputName.substringToIndex(index) == "!" {
InputName.removeAtIndex(index)
}
}
Also you can add such very helpful extension :
import Foundation
extension String{
func exclude(find:String) -> String {
return stringByReplacingOccurrencesOfString(find, withString: "", options: .CaseInsensitiveSearch, range: nil)
}
func replaceAll(find:String, with:String) -> String {
return stringByReplacingOccurrencesOfString(find, withString: with, options: .CaseInsensitiveSearch, range: nil)
}
}
you can use this:
for example if you want to remove "%" the percent from 10%
if let i = text.firstIndex(of: "%") {
text.remove(at: i) //10
}