patternswiftMinor
Water flowing swiftly over farmland – The August 2016 Community Challenge
Viewed 0 times
theaugustchallengecommunityoverflowingswiftlyfarmland2016water
Problem
This is my attempt to solve the August 2016 Community Challenge in Swift. I tried to implement the algorithm
described by @200_success:
the
all
into the lowest neighbour's
higher basin no longer exists.
The code is Swift 3 and requires Xcode 8 beta 4.
Cell.swift
Basin.swift
```
import Swift
/// A basin consists of a "sink" and zero or more cells which eventually flow
/// into the sink.
class Basin {
var cells: [Cell]
var sink: Cell
init(cell: Cell) {
cells = [cell]
sink = cell
cell.basin = self
}
func join(with other: Basin) {
precondition(other !== self, "Basin must not be joined with itself")
for cell in other.cells {
cell.basin = self
}
cells.append(contentsOf: other
described by @200_success:
- Each
Cellkeeps track of whichBasinit belongs to; eachCellis initially assume to be in its ownBasin. EachBasinhas a
sink, or lowest Cell, which acts as a "representative element" ofthe
Basin, as well as a member count. Topography keeps track ofall
Basins.- For each
Basin, find lowest of the sink's neighbours. If the lowest is not already a member of thisBasin, transfer its cells
into the lowest neighbour's
Basin, and notify Topography that thehigher basin no longer exists.
- Repeat step 2 until no further action is necessary.
- Have
Topographyenumerate theBasins and their counts.
The code is Swift 3 and requires Xcode 8 beta 4.
Cell.swift
import Swift
class Cell {
let elevation: Int
var lowerCell: Cell?
weak var basin: Basin?
init(elevation: Int) {
self.elevation = elevation
}
/// Compare the elevation of the (currently) lowest neighbor with that of
/// the other cell, and update `lowerCell` if necessary. Returns `true`
/// if other cell was lower, and `false` otherwise.
@discardableResult
func updateLowerCell(with other: Cell) -> Bool {
if other.elevation < (lowerCell?.elevation) ?? elevation {
lowerCell = other
return true
}
return false
}
}Basin.swift
```
import Swift
/// A basin consists of a "sink" and zero or more cells which eventually flow
/// into the sink.
class Basin {
var cells: [Cell]
var sink: Cell
init(cell: Cell) {
cells = [cell]
sink = cell
cell.basin = self
}
func join(with other: Basin) {
precondition(other !== self, "Basin must not be joined with itself")
for cell in other.cells {
cell.basin = self
}
cells.append(contentsOf: other
Solution
I think this code is great. Using weak references to refer to a
Basin is a great way to prevent retain cycles with automatic reference counting. One suggestion I have is to make Topography a class, because it refers to a class. Other suggestions are in the code, marked with "CR". You can use this snippet of code:/// A topography is a collection of basins. The union of all the
/// basins cells is always the complete grid.
class Topography {
var basins: [Basin]
/// Create topography from a sequence of cells. Initially, each basin
/// consists of a single cell.
init(cells: S)
where S.Element == Cell { // CR: S.Iterator.Element is the same as S.Element
basins = cells.map(Basin.init)
}
/// Create topography from 2D elevation map.
convenience init(elevationData: [[Int]]) { // CR: This calls the designated initializer, so it should be a convenience initializer.
// Create [[Cell]] array from elevationData.
let grid = elevationData.map { $0.map(Cell.init) } // CR: levelRow in clutters code, implicit names are better for simple lambdas.
// Find the lowest neighbor for each cell: left/right ...
for gridRow in grid {
for (leftCell, rightCell) in zip(gridRow, gridRow.dropFirst()) {
if !leftCell.updateLowerCell(with: rightCell) {
rightCell.updateLowerCell(with: leftCell)
}
}
}
// ... and top/down.
for (upperRow, lowerRow) in zip(grid, grid.dropFirst()) {
for (upperCell, lowerCell) in zip(upperRow, lowerRow) {
if !upperCell.updateLowerCell(with: lowerCell) {
lowerCell.updateLowerCell(with: upperCell)
}
}
}
self.init(cells: grid.flatten())
}
/// Join each basin with the next one that it is flowing into.
func joinBasins() -> Bool { // CR: classes are mutable by default, also, included return type
basins = basins.filter { basin in
if let otherBasin = basin.sink.lowerCell?.basin, otherBasin !== basin {
otherBasin.join(with: basin)
return false
}
return true // CR: Removed else, return exits
}
}
/// Sizes of all basins, sorted in decreasing order.
func basinSizes() -> [Int] {
return basins.map { $0.cells.count }.sorted(by: >)
}
}
`
Context
StackExchange Code Review Q#137844, answer score: 4
Revisions (0)
No revisions yet.