Swift Solution to Dave Thomas’s Data Munging Kata Part 3

Keeping things DRY

In part 3, we’re asked to DRY (Don’t Repeat Yourself) up the code from parts 1 and 2. My full solution can be found here.

Take the two programs written previously and factor out as much common code as possible, leaving you with two smaller programs and some kind of shared functionality.

The assignments in Parts 1 and 2 were very similar. Each required that we load a file, extract the file’s text into a table, clean each row, choose the columns we’re interested in, and find and print the row’s name with the smallest difference between two columns.

I interpreted Dave’s instructions ‘leaving you with two smaller programs’ to mean that I should create two versions of my ‘Executor’ struct, one for weather and the other for football data. In a real project, I would consolidate into one struct that accepts closures as parameters for each method that differed between the two data types.

Given my slightly damp ‘letter of the law’ approach, there were two methods that needed to be refactored.

removeUnneededColumns()

Because each file has different columns we need to use, they can no longer be hard coded. My approach was to include a validColumnIndexes: [Int] parameter. We then iterate over each index and return a map of the cell in that row.

static func removeUnneededColumns(fromTable table: [[String]], validColumnIndexes: [Int]) -> [[String]] {
return table.map{ (row: [String]) in
return takeNeededColumns(row: row, validColumnIndexes: validColumnIndexes)
}
}
private static func takeNeededColumns(row row: [String], validColumnIndexes: [Int]) -> [String] {
return validColumnIndexes.map{ row[$0] }
}

.evaluate()

Our evaluator method also needs to change because before it accepted an array of a specific type. To solve this, I created a DifferenceCalculatable protocol that both WeatherRecord and FootballResult conform to.

protocol DifferenceCalculatable {
var name: String { get }
var difference: Double { get }
}

This allows us to create a generic evaluate method that’s type constrained to DifferenceCalculatable, which gives us access to .difference and .name,

static func evaluate<Element: DifferenceCalculatable>(items items: [Element]) -> String? {
let smallestDelta = items.minElement{ $0.difference < $1.difference }
guard let name = smallestDelta?.name else { return nil }
return “\(name)”
}

Conforming to DifferenceCalculatable means that I had to change WeatherRecord’s let dayOfMonthIndex: Int to name: String and its corresponding init method.

remember to keep it dry