Skip to content

Instantly share code, notes, and snippets.

@airspeedswift
Last active August 29, 2015 14:09
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save airspeedswift/b349c256e90da746b852 to your computer and use it in GitHub Desktop.
Save airspeedswift/b349c256e90da746b852 to your computer and use it in GitHub Desktop.
Luhn Algorithm for Credit Card Validation using lazy() in Swift
// 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) ? "👍" : "👎" )
@robrix
Copy link

robrix commented Nov 13, 2014

Citing mine as “saner”? I’m flattered, don’t get me wrong 😁

@pyrtsa
Copy link

pyrtsa commented Nov 13, 2014

Bikeshedding… but as a name, mapOptional would be more in line with the idea of mapMaybe.

@airspeedswift
Copy link
Author

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