So a while back I wrote about using some functional programming techniques in Swift to solve the Luhn credit card checksum problem.
One of the suggestions in the article was defining and using |
> to avoid the annoying tennis-match bouncing left and right between free functions and methods.
Since 2.0 came out, I’ve been thinking I really needed to update some of my articles to the new syntax, and started on this one. Except as protocol extensions are the new hotness, using them would probably be the more “Swift-y” way of writing this. So here’s a solution using new 2.0 features.
As a reminder, here’s the requirements, in the form of wikipedia’s description of the algorithm:
- From the rightmost digit, which is the check digit, moving left, double the value of every second digit; if the product of this doubling operation is greater than 9 (e.g., 8 × 2 = 16), then sum the digits of the products (e.g., 16: 1 + 6 = 7, 18: 1 + 8 = 9).
- Take the sum of all the digits.
- If the total modulo 10 is equal to 0 (if the total ends in zero) then the number is valid according to the Luhn formula; else it is not valid.
So, here in turn are each of the components, all written as extensions.
First, converting characters to integers. Similar to String.toInteger
becoming Int.init(String)
, here’s an extension of Int
:
extension Int { init?(c: Character) { guard let i = Int(String(c)) else { return nil } self = i } }
Here I’m using guard
to check the value is convertible and return nil
if not. Yes, I know, this is hardly different from an if let/else
, but I like the use of guard
whenever I want to handle a failure case like this “up front”.
It would be nice to make this a protocol extension rather than an extension of Int
. All the std lib integer types support a from-string initializer, after all. But this would involve creating a protocol like IntegerStringConvertible
and then extending all the integers to conform to it, since such a common grouping doesn’t exist.
The previous post defined mapSome
, which takes a transformation function that returns an optional, and returns an array of only those values that weren’t transformed into nil
:
extension SequenceType { func mapSome<U>(transform: Generator.Element -> U?) -> [U] { var result: [U] = [] for case let x? in lazy(self).map(transform) { result.append(x) } return result } }
(the for case let x?
is using the new pattern-matching syntax that lets you for
over only the non-nil values in a sequence)
But as of 2.0, flatMap
has been enhanced to do this exact thing. So we no longer need to define it.
We’re going to use this along with the character-to-integer initializer to extract only the numbers from the credit-card string, like so, using the new 2.0b2 feature of using an init
as a function:
":123-456:".characters.flatMap(Int.init) // returns [1,2,3,4,5,6]
(note, we have to do this on the .characters
view now, since String
is no longer itself a sequence).
Next, the a function that let you apply a transformation only to every n th value in a sequence:
extension SequenceType { func mapEveryNth(n: Int, transform: Generator.Element -> Generator.Element) -> [Generator.Element] { // enumerate starts from zero, so for this to work with the nth element, // and not the 0th, n+1th etc, we need to add 1 to the ifIndex check: let isNth = { ($0 + 1) % n == 0 } return enumerate().map { i, x in isNth(i) ? transform(x) : x } } }
Then, a sum
on any sequence that contains integers:
extension SequenceType where Generator.Element: IntegerType { func sum() -> Generator.Element { return reduce(0, combine: +) } }
and an extension on any integer to check if it’s a multiple of another:
extension IntegerType { func isMultipleOf(of: Self) -> Bool { return self % of == 0 } }
And finally, to put them all together into a single extension to String
that validates the checksum:
extension String { func luhnchecksum() -> Bool { return characters .flatMap(Int.init) .reverse() .mapEveryNth(2) { $0 < 5 ? $0*2 : $0*2 - 9 } .sum() .isMultipleOf(10) } } let ccnum = "4012 8888 8888 1881" print( ccnum.luhnchecksum() ? "👍" : "👎" )
This now looks very similar to the version that used the |
> operator – but without having to define any custom operator at all. Which feels like a win to me.
Yes, you could have done all this before using extensions of concrete objects in 1.2. But most of these extensions are more general than that – for example, mapSome
can work on the string character view, but also arrays, dictionaries, sets.
Anyway, now back to playing with beta 2…