Last active
August 29, 2015 14:09
-
-
Save airspeedswift/b349c256e90da746b852 to your computer and use it in GitHub Desktop.
Luhn Algorithm for Credit Card Validation using lazy() in Swift
This file contains 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
// See @SwiftLDN's tweet for a short description of the Luhn algorithm: | |
// https://twitter.com/SwiftLDN/status/529412592834338817 | |
// or Wikipedia for a longer one. | |
// http://en.wikipedia.org/wiki/Luhn_algorithm | |
// | |
// Only meant to show extending Swift's lazy() for infotainment purposes, | |
// not necessarily recommending best or even good practices! | |
// | |
// Alternative, saner, solutions here: | |
// https://gist.github.com/alskipp/55b28891412ae0cf0d51 | |
// and here: | |
// https://github.com/robrix/credit-card-validation | |
// 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 } | |
} | |
let double = { $0*2 } | |
let combineDoubleDigits = { | |
(10...18).contains($0) ? $0-9 : $0 | |
} | |
let ccnum = "4012 8888 8888 1881" | |
let checksum = { (cc: String) -> Bool in | |
lazy(cc).reverse().mapSome(charToInt).mapEveryN(2, combineDoubleDigits • double) | |
|> sum | |
|> isMultipleOf(10) | |
} | |
println( checksum(ccnum) ? "👍" : "👎" ) | |
Bikeshedding… but as a name, mapOptional
would be more in line with the idea of mapMaybe
.
True, mapOptional would mirror the name mapMaybe but I prefer mapSome since it seems more descriptive of what it actually does. (I'd probably prefer mapJust in Haskell, tho it's less good)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Citing mine as “saner”? I’m flattered, don’t get me wrong 😁