import Foundation
//class RGBArrays : RGBArraysProtocol {
// Essential for file exists
fileprivate let dfm = FileManager.default
//MARK: Parsing String tables (from string or URL, delimited or columnar)
/// Return [[String]]? from columnar file passsed as path
func stringTable(contentsOfFile filePath: String, columns cols: [Int]) -> [[String]]? {
guard dfm.fileExists(atPath: filePath) else { return nil }
let expectedTotalLength = cols.reduce(0, { $0 + $1} )
//
// Try to read file as lines, then keep only the ones that have the proper length
let lines:[String]
do {
// Get all lines, keeping only non-empty ones
let allLines = try String(contentsOfFile: filePath,
encoding: String.Encoding.utf8)
.components(separatedBy: "\n")
.filter { !$0.isEmpty }
// Get lines with correct length
lines = allLines.filter({ $0.count == expectedTotalLength })
//If lines were discarded, warn user
if allLines.count != lines.count {
print("WARNING: \(allLines.count - lines.count) had incorrect lengths and were discarded")
}
} catch {
print(error)
return nil
}
// Now split lines into tokens, trim them and add them to the final result
var result:[[String]]?
for numRow in 0..<lines.count {
let row = lines[numRow]
var firstIndex = row.startIndex
var lastIndex = row.index(firstIndex, offsetBy: cols[0])
var range = firstIndex..<lastIndex
var frags = [String]()
frags.append(row[range].trimmingCharacters(in: CharacterSet.whitespaces))
for i in 1..<cols.count {
firstIndex = lastIndex
lastIndex = row.index(firstIndex, offsetBy: cols[i])
range = firstIndex..<lastIndex
frags.append(row[range].trimmingCharacters(in: CharacterSet.whitespaces))
}
result?.append(frags)
}
return result
} // End of stringTable(contentsOfFile:columns:)
/// /// Return [[String]]? from CSV file passsed as path
func stringTable(contentsOfFile filePath: String, delimiter del: String) -> [[String]]? {
// If file does not exist, bail out
guard dfm.fileExists(atPath: filePath) else { return nil }
// Transform the contents of the file into a [[String]] if possible or return nil
let result:[[String]]?
do {
result = try String(contentsOfFile: filePath,
encoding: String.Encoding.utf8)
.components(separatedBy: "\n")
.map { return $0.components(separatedBy: del) }
} catch {
print(error)
result = nil
}
return result
}
/// /// Return [[String]]? from columnar file passsed as URL
func stringTable(contentsOf fileURL: URL, columns cols: [Int]) -> [[String]]? {
let filePath = fileURL.path
return stringTable(contentsOfFile: filePath, columns: cols)
}
/// /// Return [[String]]? from CSV file passsed as URL
func stringTable(contentsOf fileURL: URL, delimiter del: String) -> [[String]]? {
let filePath = fileURL.path
return stringTable(contentsOfFile: filePath, delimiter: del)
}
//MARK: Parsing Double tables (from string or URL, delimited or columnar)
/// Return [[Double]]? from columnar file passsed as path
func doubleTable(contentsOfFile filePath: String, columns cols: [Int]) -> [[Double]]? {
var result:[[Double]]? = nil
if let table = stringTable(contentsOfFile: filePath, columns: cols) {
var tmp = [[Double]]()
for stringRow in table {
var numberRow = [Double]()
for s in stringRow {
if let x = Double(s) {
numberRow.append(x)
} else {
print("ERROR: item \(s) is not a valid Double")
return result
}
} // End of for s in stringRow
tmp.append(numberRow)
} // End of for stringRow in table
result = tmp
} // End of if let table = stringTable(contentsOfFile:columns:)
else {
print("\n\nERROR in doubleTable(contentsOfFile:columns:) --> Could not read \(filePath)")
}
return result
} // End of doubleTable(contentsOfFile:columns:)
/// Return [[Double]]? from CSV file passsed as path
func doubleTable(contentsOfFile filePath: String, delimiter del: String) -> [[Double]]? {
var result:[[Double]]? = nil
if let table = stringTable(contentsOfFile: filePath, delimiter: del) {
var tmp = [[Double]]()
for stringRow in table {
var numberRow = [Double]()
for s in stringRow {
if let x = Double(s) {
numberRow.append(x)
} else {
print("ERROR: item \(s) is not a valid Double")
return result
}
} // End of for s in stringRow
tmp.append(numberRow)
} // End of for stringRow in table
result = tmp
} // End of if let table = stringTable(contentsOfFile:columns:)
else {
print("\n\nERROR in doubleTable(contentsOfFile:delimiter:) --> Could not read \(filePath)")
}
return result
}
/// Return [[Double]]? from columnar file passsed as URL
func doubleTable(contentsOf fileURL: URL, columns cols: [Int]) -> [[Double]]? {
let filePath = fileURL.path
return doubleTable(contentsOfFile: filePath, columns: cols)
}
/// Return [[Double]]? from CSV file passsed as path
func doubleTable(contentsOf fileURL: URL, delimiter del: String) -> [[Double]]? {
let filePath = fileURL.path
return doubleTable(contentsOfFile: filePath, delimiter: del)
}
//MARK: Parsing Float tables (from string or URL, delimited or columnar)
/// Return [[Float]]? from columnar file passsed as path
func floatTable(contentsOfFile filePath: String, columns cols: [Int]) -> [[Float]]? {
var result:[[Float]]? = nil
if let table = stringTable(contentsOfFile: filePath, columns: cols) {
var tmp = [[Float]]()
for stringRow in table {
var numberRow = [Float]()
for s in stringRow {
if let x = Float(s) {
numberRow.append(x)
} else {
print("ERROR: item \(s) is not a valid Float")
return result
}
} // End of for s in stringRow
tmp.append(numberRow)
} // End of for stringRow in table
result = tmp
} // End of if let table = stringTable(contentsOfFile:columns:)
else {
print("\n\nERROR in floatTable(contentsOfFile:columns:) --> Could not read \(filePath)")
}
return result
}
/// Return [[Float]]? from CSV file passsed as path
func floatTable(contentsOfFile filePath: String, delimiter del: String) -> [[Float]]? {
var result:[[Float]]? = nil
if let table = stringTable(contentsOfFile: filePath, delimiter: del) {
var tmp = [[Float]]()
for stringRow in table {
var numberRow = [Float]()
for s in stringRow {
if let x = Float(s) {
numberRow.append(x)
} else {
print("ERROR: item \(s) is not a valid Float")
return result
}
} // End of for s in stringRow
tmp.append(numberRow)
} // End of for stringRow in table
result = tmp
} // End of if let table = stringTable(contentsOfFile:columns:)
else {
print("\n\nERROR in floatTable(contentsOfFile:delimiter:) --> Could not read \(filePath)")
}
return result
}
/// Return [[Float]]? from columnar file passsed as URL
func floatTable(contentsOf fileURL: URL, columns cols: [Int]) -> [[Float]]? {
let filePath = fileURL.path
return floatTable(contentsOfFile: filePath, columns: cols)
}
/// Return [[String]]? from CSV file passsed as URL
func floatTable(contentsOf fileURL: URL, delimiter del: String) -> [[Float]]? {
let filePath = fileURL.path
return floatTable(contentsOfFile: filePath, delimiter: del)
}
//MARK: Parsing Int tables (from string or URL, delimited or columnar)
/// Return [[Int]]? from columnar file passsed as path
func intTable(contentsOfFile filePath: String, columns cols: [Int]) -> [[Int]]? {
var result:[[Int]]? = nil
if let table = stringTable(contentsOfFile: filePath, columns: cols) {
var tmp = [[Int]]()
for stringRow in table {
var numberRow = [Int]()
for s in stringRow {
if let x = Int(s) {
numberRow.append(x)
} else {
print("ERROR: item \(s) is not a valid Int")
return result
}
} // End of for s in stringRow
tmp.append(numberRow)
} // End of for stringRow in table
result = tmp
} // End of if let table = stringTable(contentsOfFile:columns:)
else {
print("\n\nERROR in intTable(contentsOfFile:columns:) --> Could not read \(filePath)")
}
return result
}
/// Return [[Int]]? from CSV file passsed as path
func intTable(contentsOfFile filePath: String, delimiter del: String) -> [[Int]]? {
var result:[[Int]]? = nil
if let table = stringTable(contentsOfFile: filePath, delimiter: del) {
var tmp = [[Int]]()
for stringRow in table {
var numberRow = [Int]()
for s in stringRow {
if let x = Int(s) {
numberRow.append(x)
} else {
print("ERROR: item \(s) is not a valid Int")
return result
}
} // End of for s in stringRow
tmp.append(numberRow)
} // End of for stringRow in table
result = tmp
} // End of if let table = stringTable(contentsOfFile:delimiter:)
else {
print("\n\nERROR in intTable(contentsOfFile:delimiter:) --> Could not read \(filePath)")
}
return result
}
/// Return [[Int]]? from columnar file passsed as URL
func intTable(contentsOf fileURL: URL, columns cols: [Int]) -> [[Int]]? {
let filePath = fileURL.path
return intTable(contentsOfFile: filePath, columns: cols)
}
/// Return [[Int]]? from CSV file passsed as path
func intTable(contentsOf fileURL: URL, delimiter del: String) -> [[Int]]? {
let filePath = fileURL.path
return intTable(contentsOfFile: filePath, delimiter: del)
}
//MARK: Writing String tables (to string or URL, delimited or columnar)
/// Write [[String]] to columnar file passsed as path
func writeStringTable(_ t: [[String]], toFile filePath: String, columns cols: [Int]) -> Bool {
var tmp = ""
for row in t {
for n in 0..<row.count {
tmp.append(row[n].padding(toLength: cols[n],
withPad: " ",
startingAt: 0))
}
tmp.append("\n")
} // for row in t
var didWrite = true
do {
try tmp.write(toFile: filePath,
atomically: true,
encoding: String.Encoding.utf8)
} catch {
print("ERROR: writeStringTable(_:contentsOf:columns:) could not write to \(filePath)")
didWrite = false
}
return didWrite
}
/// Write [[String]] to columnar file passsed as URL
func writeStringTable(_ t: [[String]], toURL fileURL: URL, columns cols: [Int]) -> Bool {
let filePath = fileURL.path
var tmp = ""
for row in t {
for n in 0..<row.count {
tmp.append(row[n].padding(toLength: cols[n],
withPad: " ",
startingAt: 0))
}
tmp.append("\n")
} // for row in t
var didWrite = true
do {
try tmp.write(toFile: filePath,
atomically: true,
encoding: String.Encoding.utf8)
} catch {
print("ERROR: writeStringTable(_:contentsOf:columns:) could not write to \(filePath)")
didWrite = false
}
return didWrite
}
/// Write [[String]] to CSV file passsed as URL
func writeStringTable(_ t: [[String]], toURL fileURL: URL, delimiter del: String) -> Bool {
let filePath = fileURL.path
var tmp = ""
t.forEach { tmp.append( $0.joined(separator: del)); tmp.append("\n") }
var didWrite = true
do {
try tmp.write(toFile: filePath,
atomically: true,
encoding: String.Encoding.utf8)
} catch {
print("ERROR: writeStringTable(_:contentsOf:delimiter:) could not write to \(filePath)")
didWrite = false
}
return didWrite
}
/// Write [[String]] to CSV file passsed as path
func writeStringTable(_ t: [[String]], toFile filePath: String, delimiter del: String) -> Bool {
var tmp = ""
t.forEach { tmp.append( $0.joined(separator: del)); tmp.append("\n") }
var didWrite = true
do {
try tmp.write(toFile: filePath,
atomically: true,
encoding: String.Encoding.utf8)
} catch {
print("ERROR: writeStringTable(_:contentsOf:delimiter:) could not write to \(filePath)")
didWrite = false
}
return didWrite
}
// MARK: Printing String, Int, Float or Double tables
/// Print [[String]] as columns with automatic width calculation
func printStringTable(_ t:[[String]]) {
guard
let maxColWidth = t.flatMap( { $0 } ).map( { $0.count }).max()
else { print("\n\nERROR: could not print string table\n\n"); return }
// print("Maxcolwidth is: \(maxColWidth)")
for row in t {
for column in row {
print("| \(column.padding(toLength: maxColWidth, withPad: " ", startingAt: 0)) ", terminator: "")
}
print("|")
}
}
/// Print [String] as columns with automatic width calculation
func printStringList(_ t:[String]) {
guard
let maxColWidth = t.compactMap( { $0 } ).map( { $0.count }).max()
else { print("\n\nERROR: could not print string table\n\n"); return }
// print("Maxcolwidth is: \(maxColWidth)")
for row in t {
print("| \(row.padding(toLength: maxColWidth, withPad: " ", startingAt: 0)) ", terminator: "")
}
print("|")
} // End of printStringList(_:)
/// Print [[Double]] as columns with automatic width calculation
func printDoubleTable(_ t:[[Double]]) {
guard
let maxColWidth = t.flatMap( { $0 }).map({ $0.description.count }).max()
else { print("\n\nERROR: could not print Double table\n\n"); return }
// print("Maxcolwidth is: \(maxColWidth)")
for row in t {
for column in row {
print("| \(column.description.padding(toLength: maxColWidth, withPad: " ", startingAt: 0)) ", terminator: "")
}
print("|")
}
}
/// Print [[Float]] as columns with automatic width calculation
func printFloatTable(_ t:[[Float]]) {
guard
let maxColWidth = t.flatMap( { $0 }).map({ $0.description.count }).max()
else { print("\n\nERROR: could not print Float table\n\n"); return }
// print("Maxcolwidth is: \(maxColWidth)")
for row in t {
for column in row {
print("| \(column.description.padding(toLength: maxColWidth, withPad: " ", startingAt: 0)) ", terminator: "")
}
print("|")
}
}
/// Print [[Int]] as columns with automatic width calculation
func printIntTable(_ t:[[Int]]) {
guard
let maxColWidth = t.flatMap( { $0 }).map({ $0.description.count }).max()
else { print("\n\nERROR: could not print Int table\n\n"); return }
// print("Maxcolwidth is: \(maxColWidth)")
for row in t {
for column in row {
print("| \(column.description.padding(toLength: maxColWidth, withPad: " ", startingAt: 0)) ", terminator: "")
}
print("|")
}
}
//MARK: Writing Double tables (to string or URL, delimited or columnar)
/// Write [[Double]] to columnar file passed as path
func writeDoubleTable(_ t: [[Double]], toFile filePath: String, columns cols: [Int]) -> Bool {
var tmp = ""
t.forEach {
for numCol in 0..<$0.count {
tmp.append($0[numCol].description.padding(toLength: cols[numCol], withPad: " ", startingAt: 0))
}
tmp.append("\n")
} // t.forEach
var didwrite = true
do {
try tmp.write(toFile: filePath, atomically: true, encoding: String.Encoding.utf8)
} catch {
print(error)
print("ERROR: writeDoubleTable(_:toFile:columns:) could not write to \(filePath)")
didwrite = false
}
return didwrite
} // writeDoubleTable(_:toFile:columns:)
/// Write [[Double]] to CSV file passed as path
func writeDoubleTable(_ t: [[Double]], toFile filePath: String, delimiter del: String) -> Bool {
var total = ""
t.forEach {
var tmp = [String]()
for numCol in 0..<$0.count {
tmp.append($0[numCol].description)
}
total.append(contentsOf: tmp.joined(separator: del))
total.append("\n")
} // t.forEach
var didwrite = true
do {
try total.write(toFile: filePath, atomically: true, encoding: String.Encoding.utf8)
} catch {
print(error)
print("ERROR: writeDoubleTable(_:toFile:delimiter:) could not write to \(filePath)")
didwrite = false
}
return didwrite
}
/// Write [[Double]] to columnar file passed as URL
func writeDoubleTable(_ t: [[Double]], toURL fileURL: URL, columns cols: [Int]) -> Bool {
let filePath = fileURL.path
return writeDoubleTable(t, toFile: filePath, columns: cols)
}
/// Write [[Double]] to CSV file passed as path
func writeDoubleTable(_ t: [[Double]], toURL fileURL: URL, delimiter del: String) -> Bool {
let filePath = fileURL.path
return writeDoubleTable(t, toFile: filePath, delimiter: del)
}
//MARK: Writing Float tables (to string or URL, delimited or columnar)
/// Write [[Float]] to columnar file passed as path
func writeFloatTable(_ t: [[Float]], toFile filePath: String, columns cols: [Int]) -> Bool {
var tmp = ""
t.forEach {
for numCol in 0..<$0.count {
tmp.append($0[numCol].description.padding(toLength: cols[numCol], withPad: " ", startingAt: 0))
}
tmp.append("\n")
} // t.forEach
var didwrite = true
do {
try tmp.write(toFile: filePath, atomically: true, encoding: String.Encoding.utf8)
} catch {
print(error)
print("ERROR: writeDoubleTable(_:toFile:columns:) could not write to \(filePath)")
didwrite = false
}
return didwrite
}
/// Write [[Double]] to CSV file passed as path
func writeFloatTable(_ t: [[Float]], toFile filePath: String, delimiter del: String) -> Bool {
var total = ""
t.forEach {
var tmp = [String]()
for numCol in 0..<$0.count {
tmp.append($0[numCol].description)
}
total.append(contentsOf: tmp.joined(separator: del))
total.append("\n")
} // t.forEach
var didwrite = true
do {
try total.write(toFile: filePath, atomically: true, encoding: String.Encoding.utf8)
} catch {
print(error)
print("ERROR: writeDoubleTable(_:toFile:delimiter:) could not write to \(filePath)")
didwrite = false
}
return didwrite
}
/// Write [[Double]] to columnar file passed as URL
func writeFloatTable(_ t: [[Float]], toURL fileURL: URL, column cols: [Int]) -> Bool {
let filePath = fileURL.path
return writeFloatTable(t, toFile: filePath, columns: cols)
}
/// Write [[Double]] to CSV file passed as URL
func writeFloatTable(_ t: [[Float]], toURL fileURL: URL, delimiter del: String) -> Bool {
let filePath = fileURL.path
return writeFloatTable(t, toFile: filePath, delimiter: del)
}
// MARK: Writing Int tables (to string or URL, delimited or columnar)
/// Write [[Int]] to columnar file passed as path
func writeIntTable(_ t: [[Int]], toFile filePath: String, columns cols: [Int]) -> Bool {
var tmp = ""
t.forEach {
for numCol in 0..<$0.count {
tmp.append($0[numCol].description.padding(toLength: cols[numCol], withPad: " ", startingAt: 0))
}
tmp.append("\n")
} // t.forEach
var didwrite = true
do {
try tmp.write(toFile: filePath, atomically: true, encoding: String.Encoding.utf8)
} catch {
print(error)
print("ERROR: writeDoubleTable(_:toFile:columns:) could not write to \(filePath)")
didwrite = false
}
return didwrite
}
/// Write [[Int]] to CSV file passed as path
func writeIntTable(_ t: [[Int]], toFile filePath: String, delimiter del: String) -> Bool {
var total = ""
t.forEach {
var tmp = [String]()
for numCol in 0..<$0.count {
tmp.append($0[numCol].description)
}
total.append(contentsOf: tmp.joined(separator: del))
total.append("\n")
} // t.forEach
var didwrite = true
do {
try total.write(toFile: filePath, atomically: true, encoding: String.Encoding.utf8)
} catch {
print(error)
print("ERROR: writeDoubleTable(_:toFile:delimiter:) could not write to \(filePath)")
didwrite = false
}
return didwrite
}
/// Write [[Into]] to columnar file passed as URL
func writeIntTable(_ t: [[Int]], toURL fileURL: URL, columns cols: [Int]) -> Bool {
let filePath = fileURL.path
return writeIntTable(t, toFile: filePath, columns: cols)
}
/// Write [[Int]] to CSV file passed as URL
func writeIntTable(_ t: [[Int]], toURL fileURL: URL, delimiter del: String) -> Bool {
let filePath = fileURL.path
return writeIntTable(t, toFile: filePath, delimiter: del)
}
//} // End of class RGBArrays