As per exercise, after the title, author or rating is longer than the header in main column/s, the table becomes misaligned (after correcting error: "Repetition count should be non-negative" by changing - to in line for constant paddingNeeded). I want to keep the column widths as long as the longest String in given column and adjust the spacing in other cells. I was trying to solve it within printTable function with conditional however did not manage to compare columnLabel and item as they are in different loops. Was trying also to set up new function within protocol to manage alignment. Could not figure it out, if you can advice.
protocol TabularDataSource {
var numberOfRows: Int { get }
var numberOfColumns: Int { get }
func label(forColumn column: Int) -> String
func itemFor(row: Int, column: Int) -> String
}
func printTable(_ dataSource: TabularDataSource & CustomStringConvertible) {
print("Tab. 1: \(dataSource)")
var headerRow = "|"
var columnWidths = [Int]()
for ii in 0 ..< dataSource.numberOfColumns {
let columnLabel = dataSource.label(forColumn: ii)
let columnHeader = " \(columnLabel) |"
headerRow = columnHeader
columnWidths.append(columnLabel.count)
}
print(headerRow)
for ii in 0 ..< dataSource.numberOfRows {
var out = "|"
for jj in 0 ..< dataSource.numberOfColumns {
let item = dataSource.itemFor(row: ii, column: jj)
let paddingNeeded = columnWidths[jj] - item.count
let padding = repeatElement(" ", count: paddingNeeded).joined(separator: "")
out = " \(padding)\(item) |"
}
print(out)
}
}
struct Book {
let title: String
let author: String
let rating: Int
}
struct BookCollection: TabularDataSource, CustomStringConvertible {
let name: String
var books = [Book]()
var description: String {
return ("\(name) book collection")
}
init(name: String) {
self.name = name
}
mutating func add(_ book: Book) {
books.append(book)
}
var numberOfRows: Int {
return books.count
}
var numberOfColumns: Int {
return 3
}
func label(forColumn column: Int) -> String {
switch column {
case 0: return "Title"
case 1: return "Author"
case 2: return "Rating"
default: fatalError("Invalid column!")
}
}
func itemFor(row: Int, column: Int) -> String {
let book = books[row]
switch column {
case 0: return book.title
case 1: return String(book.author)
case 2: return String(book.rating)
default: fatalError("Invalid column!")
}
}
}
var bookCollection = BookCollection(name: "Fantasy")
bookCollection.add(Book(title: "Ava", author: "Reno", rating: 7))
bookCollection.add(Book(title: "Vis", author: "Luc", rating: 7))
bookCollection.add(Book(title: "Te", author: "Julo", rating: 9))
printTable(bookCollection)
CodePudding user response:
You could add a method to your datasource to know what's the max length of the column:
protocol TabularDataSource {
func width(for column: Int) -> Int
}
Implementation:
func width(for column: Int) -> Int {
var labels = (0..<numberOfRows).map { itemFor(row: $0, column: column )}
labels.append(label(forColumn: column))
let max = labels.max(by: { $0.count < $1.count })?.count ?? 0
return max 2 //space before/after
}
Then:
func printTable(_ dataSource: TabularDataSource & CustomStringConvertible) {
var lines = "|"
for aColumn in 0..<dataSource.numberOfColumns {
let label = dataSource.label(forColumn: aColumn)
let totalOfSpaces = dataSource.width(for: aColumn) - label.count
let spaces = repeatElement(" ", count: totalOfSpaces / 2).joined(separator: "")
let additionalSpace = totalOfSpaces % 2 == 0 ? "" : " "
lines = "\(additionalSpace)\(spaces)\(label)\(spaces)|"
}
lines = "\n"
for aRow in 0..<dataSource.numberOfRows {
lines = "|"
for aColumn in 0..<dataSource.numberOfColumns {
let label = dataSource.itemFor(row: aRow, column: aColumn)
let totalOfSpaces = dataSource.width(for: aColumn) - label.count
let additionalSpace = totalOfSpaces % 2 == 0 ? "" : " "
let spaces = repeatElement(" ", count: totalOfSpaces / 2).joined(separator: "")
lines = "\(additionalSpace)\(spaces)\(label)\(spaces)|"
}
lines = "\n"
}
print(lines)
}
Which could be factorized into:
func printTable(_ dataSource: TabularDataSource & CustomStringConvertible) {
func text(text: String, for width: Int) -> String {
let totalOfSpaces = width - text.count
let spaces = repeatElement(" ", count: totalOfSpaces / 2).joined(separator: "")
let additionalSpace = totalOfSpaces % 2 == 0 ? "" : " "
return "\(additionalSpace)\(spaces)\(text)\(spaces)|"
}
lines = "|"
for aColumn in 0..<dataSource.numberOfColumns {
lines = text(text: dataSource.label(forColumn: aColumn), for: dataSource.width(for: aColumn))
}
var lines = "\n"
for aRow in 0..<dataSource.numberOfRows {
lines = "|"
for aColumn in 0..<dataSource.numberOfColumns {
lines = text(text: dataSource.itemFor(row: aRow, column: aColumn), for: dataSource.width(for: aColumn))
}
lines = "\n"
}
print(lines)
}
