Last active
August 29, 2015 14:09
-
-
Save airspeedswift/e584239d7658b317f59a to your computer and use it in GitHub Desktop.
Luhn Algorithm – side by side lazy and eager versions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Another version of the Luhn algorithm, similar to the one found here: | |
// https://gist.github.com/airspeedswift/b349c256e90da746b852 | |
// | |
// This time, trying to keep two versions, one eager one lazy, | |
// as similar as possible. Only adding "lazy" to the start of | |
// the expression to switch between the two. | |
// | |
// Much of the same code as the previous version at the top, | |
// Skip down to line 110 for the different part | |
// mapSome is my Swift version of Haskell's mapMaybe, which | |
// is a map that takes a transform function that returns an | |
// optional, and returns a collection of only those values | |
// that weren't nil | |
// first we need a lazy view that holds the original | |
// sequence and the transform function | |
struct MapSomeSequenceView<Base: SequenceType, T> { | |
private let _base: Base | |
private let _transform: (Base.Generator.Element) -> T? | |
} | |
// extend it to implement SequenceType | |
extension MapSomeSequenceView: SequenceType { | |
typealias Generator = GeneratorOf<T> | |
func generate() -> Generator { | |
var g = _base.generate() | |
// GeneratorOf is a helper that takes a | |
// closure and calls it to generate each | |
// element | |
return GeneratorOf { | |
while let element = g.next() { | |
if let some = self._transform(element) { | |
return some | |
} | |
} | |
return nil | |
} | |
} | |
} | |
// now extend a lazy collection to return that view | |
// from a call to mapSome. In pracice, when doing this, | |
// you should do it for all the lazy wrappers | |
// (i.e. random-access, forward and sequence) | |
extension LazyBidirectionalCollection { | |
// I might be missing a trick with this super-ugly return type, is there a better way? | |
func mapSome<U>(transform: (S.Generator.Element) -> U?) -> LazySequence<MapSomeSequenceView<LazyBidirectionalCollection<S>,U>> { | |
return lazy(MapSomeSequenceView(_base: self, _transform: transform)) | |
} | |
} | |
// curried function - call with 1 argument to get a function | |
// that tells you if i is a multiple of a given number | |
// e.g. | |
// let isEven = isMultipleOf(2) | |
// isEven(4) // true | |
func isMultipleOf<T: IntegerType>(of: T)->T->Bool { | |
return { $0 % of == 0 } | |
} | |
// extend LazySequence to map only every nth element, with all | |
// other elements untransformed. | |
extension LazySequence { | |
func mapEveryN(n: Int, _ transform: (S.Generator.Element) -> S.Generator.Element) -> LazySequence<MapSequenceView<EnumerateSequence<LazySequence<S>>,S.Generator.Element>> { | |
let isNth = isMultipleOf(n) | |
return lazy(enumerate(self)).map { (pair: (index: Int, elem: S.Generator.Element)) -> S.Generator.Element in | |
isNth(pair.index+1) | |
? transform(pair.elem) | |
: pair.elem | |
} | |
} | |
} | |
infix operator |> { | |
associativity left | |
} | |
func |><T,U>(t: T, f: (T)->U) -> U { | |
return f(t) | |
} | |
infix operator • { | |
associativity left | |
} | |
func • <T, U, V> (g: U -> V, f: T -> U) -> T -> V { | |
return { x in g(f(x)) } | |
} | |
// function to free a method from the shackles | |
// of it's owner | |
func freeMemberFunc<T,U>(f: T->()->U)->T->U { | |
return { (t: T)->U in f(t)() } | |
} | |
// stringToInt can now be pipelined or composed | |
let stringToInt = freeMemberFunc(String.toInt) | |
// if only Character also had a toInt method | |
let charToString = { (c: Character) -> String in String(c) } | |
let charToInt = stringToInt • charToString | |
func sum<S: SequenceType where S.Generator.Element: IntegerType>(nums: S)->S.Generator.Element { | |
return reduce(nums, 0) { $0.0 + $0.1 } | |
} | |
// Free versions of various lazy member functions: | |
func reverse<T>(source: LazyBidirectionalCollection<T>) -> LazyBidirectionalCollection<BidirectionalReverseView<T>> { | |
return source.reverse() | |
} | |
func map<S: SequenceType, U>(source: LazySequence<S>, transform: (S.Generator.Element)->U) -> LazySequence<MapSequenceView<S,U>> { | |
return source.map(transform) | |
} | |
func mapSome<C: CollectionType,U>(source: LazyBidirectionalCollection<C>, transform: (C.Generator.Element)->U?) -> LazySequence<MapSomeSequenceView<LazyBidirectionalCollection<C>, U>> { | |
return source.mapSome(transform) | |
} | |
func mapEveryN<S: SequenceType>(source: LazySequence<S>, n: Int, transform: (S.Generator.Element)->S.Generator.Element) -> LazySequence<MapSequenceView<EnumerateSequence<LazySequence<S>>,S.Generator.Element>> { | |
return source.mapEveryN(n, transform) | |
} | |
// Non-lazy version of mapSome: | |
func mapSome<S: SequenceType, C: ExtensibleCollectionType>(source: S, transform: (S.Generator.Element)->C.Generator.Element?) -> C { | |
var result = C() | |
for x in source { | |
if let y = transform(x) { | |
result.append(y) | |
} | |
} | |
return result | |
} | |
// Specialized default version of mapSome that returns an array, to avoid | |
// forcing the user having to specify: | |
func mapSome<S: SequenceType,U>(source: S, transform: (S.Generator.Element)->U?)->[U] { | |
// just calls the more generalized version | |
return mapSome(source, transform) | |
} | |
// Non-lazy version of mapEveryN: | |
func mapEveryN<S: SequenceType>(source: S, n: Int, transform: (S.Generator.Element) -> S.Generator.Element) -> [S.Generator.Element] { | |
let isNth = isMultipleOf(n) | |
return map(enumerate(source)) { (pair: (index: Int, elem: S.Generator.Element)) in | |
isNth(pair.index+1) | |
? transform(pair.elem) | |
: pair.elem | |
} | |
} | |
let double = { $0*2 } | |
let combineDoubleDigits = { | |
(10...18).contains($0) ? $0-9 : $0 | |
} | |
// first, the lazy version of checksum calcuation | |
let lazychecksum = { (ccnum: String) -> Bool in | |
ccnum | |
|> lazy | |
|> reverse | |
|> { mapSome($0, charToInt) } | |
|> { mapEveryN($0, 2, double) } | |
|> { map($0, combineDoubleDigits) } | |
|> sum | |
|> isMultipleOf(10) | |
} | |
// removing lazy at start of pipeline | |
// switches to array-based version | |
let eagerchecksum = { (ccnum: String) -> Bool in | |
ccnum | |
// |> lazy | |
|> reverse | |
|> { mapSome($0, charToInt) } | |
|> { mapEveryN($0, 2, double) } | |
|> { map($0, combineDoubleDigits) } | |
|> sum | |
|> isMultipleOf(10) | |
} | |
let ccnum = "4012 8888 8888 1881" | |
print( lazychecksum(ccnum) ? "👍" : "👎" ) | |
print( eagerchecksum(ccnum) ? "👍" : "👎" ) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment