No set up for this, let’s just dive into the challenge!
Silver Challenge
The key for this challenge is computing the widths of the columns before they are used to determine padding in the columns themselves. To accomplish this, I created a separate function to compute the widths and return the [Int]
array needed to determine padding within the printing loops. There is probably a more concise way to do this, but I had neither the time nor the patience to do it. essentially all it does is go through the labels, compute the widths, and then go through the rows, and override the widths if any of the items within the row are larger than the width provided by the label.
func computeWidths(for dataSource: TabularDataSource) -> [Int] {
var columnWidths = [Int]()
for i in 0 ..< dataSource.numberOfColumns {
let columnLabel = dataSource.label(forColumn: i)
columnWidths.append(columnLabel.characters.count)
}
for i in 0 ..< dataSource.numberOfRows {
for j in 0 ..< dataSource.numberOfColumns {
let item = dataSource.itemFor(row: i, column: j)
if columnWidths[j] < item.characters.count {
columnWidths[j] = item.characters.count
}
}
}
return columnWidths
}
Now all you have to do is assign the variable in the printTable(_:)
function, and use that value to assign padding within the labels AND the items within the rows. There is a bit of refactoring here, but it’s very similar to the code done in the chapter.
func printTable(_ dataSource: TabularDataSource & CustomStringConvertible) {
print("Table: \(dataSource.description)")
var firstRow = "|"
var columnWidths = computeWidths(for: dataSource)
// Heading Labels
for i in 0 ..< dataSource.numberOfColumns {
let columnLabel = dataSource.label(forColumn: i)
let paddingNeeded = columnWidths[i] - columnLabel.characters.count
let padding = repeatElement(" ", count: paddingNeeded).joined()
let columnHeader = " \(padding)\(columnLabel) |"
firstRow += columnHeader
}
print(firstRow)
// Row data
for i in 0 ..< dataSource.numberOfRows {
// Start the output string
var out = "|"
// Append each item in this row to the string
for j in 0 ..< dataSource.numberOfColumns {
let item = dataSource.itemFor(row: i, column: j)
var paddingNeeded = columnWidths[j] - item.characters.count
if paddingNeeded < 0 { paddingNeeded = 0 }
let padding = repeatElement(" ", count: paddingNeeded).joined(separator: "")
out += " \(padding)\(item) |"
}
// Done - print the string
print(out)
}
}
Gold Challenge
This challenge was actually easier to complete than the Silver challenge, IMO. The BookCollection
I modeled almost identically to the Department
struct, implementing the same protocols with just minor property details that were changed. Although the book said that it needed to conform to TabularDataSource
, it also needed to conform to CustomStringConvertible
in order to be printed (since the printTable(_:)
function conforms to both). I also created a Book
struct, similar to the Person
struct, to hold the same kind of information.
struct Book {
let title: String
let author: String
let averageRating: Double
}
struct BookCollection: TabularDataSource, CustomStringConvertible {
let name: String
var books = [Book]()
var numberOfRows: Int { return books.count }
var numberOfColumns: Int { return 3 }
var description: String { return "Book Collection (\(name))" }
init(name: String) {
self.name = name
}
mutating func add(_ book: Book) {
books.append(book)
}
func label(forColumn column: Int) -> String {
switch column {
case 0: return "Title"
case 1: return "Author"
case 2: return "Average 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 book.author
case 2: return String(book.averageRating)
default: fatalError("Invalid column!")
}
}
}
Testing:
var bookCollection = BookCollection(name: "Harry Potter Series")
bookCollection.add(Book(title: "Harry Potter and the Sorcerer's Stone", author: "J. K. Rowling", averageRating: 4.5))
bookCollection.add(Book(title: "Harry Potter and the Chamber of Secrets", author: "J. K. Rowling", averageRating: 4.2))
bookCollection.add(Book(title: "Harry Potter and the Prisoner of Azkaban", author: "J. K. Rowling", averageRating: 4.7))
bookCollection.add(Book(title: "Harry Potter and the Goblet of Fire", author: "J. K. Rowling", averageRating: 5.0))
bookCollection.add(Book(title: "Harry Potter and the Order of the Pheonix", author: "J. K. Rowling", averageRating: 3.5))
bookCollection.add(Book(title: "Harry Potter and the Half Blood Prince", author: "J. K. Rowling", averageRating: 4.75))
bookCollection.add(Book(title: "Harry Potter and the Deathly Hollows", author: "J. K. Rowling", averageRating: 4.95))
printTable(bookCollection)
Results (ignore the syntax-highlighting):
Table: Book Collection (Harry Potter Series)
| Title | Author | Average Rating |
| Harry Potter and the Sorcerers Stone | J. K. Rowling | 4.5 |
| Harry Potter and the Chamber of Secrets | J. K. Rowling | 4.5 |
| Harry Potter and the Prisoner of Azkaban | J. K. Rowling | 4.5 |
| Harry Potter and the Goblet of Fire | J. K. Rowling | 4.5 |
| Harry Potter and the Order of the Pheonix | J. K. Rowling | 4.5 |
| Harry Potter and the Half Blood Prince | J. K. Rowling | 4.5 |
| Harry Potter and the Deathly Hollows | J. K. Rowling | 4.5 |
Feel free to share your own solutions!