Skip to content

Instantly share code, notes, and snippets.

@airspeedswift
Last active August 29, 2015 14:09
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save airspeedswift/e584239d7658b317f59a to your computer and use it in GitHub Desktop.
Save airspeedswift/e584239d7658b317f59a to your computer and use it in GitHub Desktop.
Luhn Algorithm – side by side lazy and eager versions
// 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