Reputation:
I've been working on Exercism.io's Swift track, after refactoring my solution for the BinarySearchTree problem to smallish code, I noticed a pattern that seems like it could be refactored. I think the answer is "No, you can't do that," but I figured it was worth asking.
typealias BinarySearchTree = BST
indirect enum BST< T: Comparable> {
case N( T, BST, BST )
case E
init( _ v: T ) { self = .N( v, .E, .E ) }
mutating func insert( _ n: T ) {
if case .N( let v, var l, var r ) = self {
if n <= v { l.insert( n ) } else { r.insert( n ) }
self = .N( v, l, r )
} else { self = .N( n, .E, .E ) }
}
var data: T? { if case let .N( v, _, _ ) = self { return v } else { return nil } }
var left: BST? { if case let .N( _, l, _ ) = self { return l } else { return nil } }
var right: BST? { if case let .N( _, _, r ) = self { return r } else { return nil } }
func allData() -> [ T ] { if case let .N( v, l, r ) = self { return l.allData() + [ v ] as [ T ] + r.allData() } else { return [ T ]() } }
}
My focus is on var
s data
, left
, and right
. See how they're almost spitting images of each other? Not that it should be done, but could it be done: Write a function that accepts T
or BST
through one parameter, a tuple position through another (Int
), and returns a function or closure that enum decomposition and returns the captured value of passed in type?
I realize the short names and one-liner-ness of my code is far past the point of silly. It emerged after a few iterations over this initial solution, which I think you'll agree is more readable, though the pattern is less noticeable:
indirect enum BinarySearchTree< T: Comparable> {
case Node( T, BinarySearchTree, BinarySearchTree )
case None
init( _ value: T ) { self = .Node( value, .None, .None ) }
mutating func insert( _ new: T ) {
if case .Node( let value, var left, var right ) = self {
if new <= value { left.insert( new ) }
else { right.insert( new ) }
self = .Node( value, left, right )
} else { self = .Node( new, .None, .None ) }
}
var data: T? {
if case let .Node( value, _, _ ) = self { return value }
else { return nil }
}
func allData() -> [ T ] {
if case let .Node( value, left, right ) = self {
return left.allData() + [ value ] as [ T ] + right.allData()
} else { return [ T ]() }
}
var left: BinarySearchTree? {
if case let .Node( _, left, _ ) = self { return left }
else { return nil }
}
var right: BinarySearchTree? {
if case let .Node( _, _, right ) = self { return right }
else { return nil }
}
}
Upvotes: 1
Views: 51
Reputation: 299495
This can absolutely be simplified, and probably should be, with a helper function:
private var vlr: (T, BST, BST)? {
switch self {
case let .N(v, l, r): return (v,l,r)
case .E: return nil
}
}
var data: T? { return vlr?.0 }
var left: BST? { return vlr?.1 }
var right: BST? { return vlr?.2 }
I build these kinds of helpers on enums to turn associated data into Optionals all the time (in some cases as a public method).
This can be used in allData
, too, which I'd probably write this way (chained +
is notorious for creating compile problems in Swift, and can be extremely runtime inefficient due to creating intermediate copies):
func allData() -> [ T ] {
if let (v, l, r) = vlr {
var result = l.allData()
result.append(v)
result.append(contentsOf: r.allData())
return result
}
return []
}
Upvotes: 1