Skip to content

Instantly share code, notes, and snippets.

@ebluehands
Last active December 18, 2015 08:32
Show Gist options
  • Save ebluehands/1e36e41422e729949ff8 to your computer and use it in GitHub Desktop.
Save ebluehands/1e36e41422e729949ff8 to your computer and use it in GitHub Desktop.
This is an implementation of the Luhn mod N algorithm. A repo is also available : https://github.com/ebluehands/LuhnModN
import Darwin
enum LuhnError : ErrorType {
case InvalidCharacters
case ModuloOutOfRange
}
/// This is an implementation of the [Luhn mod N algorithm](https://en.wikipedia.org/wiki/Luhn_mod_N_algorithm)
public class LuhnModN {
// MARK: Public
/**
Check if a string is valid
- Parameter s: The string to validate
- Parameter modulo: The modulo in which to compute. It corresponds to the number of valid characters
- Returns: true if the string is valid, false otherwise
*/
public static func isValid(s : String, modulo : Int = 10) -> Bool {
guard validateModulo(modulo) && !containsInvalidCharacters(s, modulo: Int32(modulo)) else { return false }
let sum = calculateLuhnSum(s, modulo: modulo, shouldDouble: { ($0 % 2 != 0) })
let remainder = sum % modulo
return remainder == 0
}
/**
Add the check character
- Parameter s: The string to add the check character
- Parameter modulo: The modulo in which to compute. It corresponds to the number of valid characters
- Throws: LuhnError
- Returns: The string with the check character
*/
public static func addCheckCharacter(s : String, modulo : Int = 10) throws -> String {
guard validateModulo(modulo) else { throw LuhnError.ModuloOutOfRange }
guard !containsInvalidCharacters(s, modulo: Int32(modulo)) else { throw LuhnError.InvalidCharacters }
return s + generateCheckCharacter(s, modulo : modulo)
}
// MARK: Private
/**
Check if a string contains invalid characters
- Parameter s : The string to check
- Parameter modulo: The modulo in which to compute. It corresponds to the number of valid characters
- Returns: true if one or more invalid characters were found, true otherwise
*/
private static func containsInvalidCharacters(s : String, modulo : Int32) -> Bool {
return [Character](s.characters)
.reduce(false) { $0 || (strtoul(String($1), nil, modulo) == 0 && $1 != "0") }
}
/**
Validate the modulo. The modulo should be between 2 and 36 inclusive
- Parameter modulo: The modulo
- Returns: true if the modulo is valid, false otherwise
*/
private static func validateModulo(modulo : Int) -> Bool {
return modulo > 1 && modulo < 37
}
/**
Get the int value of a character
- Parameter character: The character
- Parameter modulo: The modulo in which to compute. It corresponds to the number of valid characters
- Returns: The int value of the character
*/
private static func codePointFromCharacter(character : Character, modulo : Int32) -> Int {
return Int(strtoul(String(character), nil, modulo))
}
/**
Get the string value of an int
- Parameter codePoint: The int to convert
- Parameter modulo: The modulo in which to compute. It corresponds to the number of valid characters
- Returns: The string value of the integer
*/
private static func characterFromCodePoint(codePoint : Int, modulo : Int) -> String {
return String(codePoint, radix: modulo)
}
/**
Sum the digits of a number
- Parameter addend: The number
- Parameter modulo: The modulo in which to compute. It corresponds to the number of valid characters
- Returns : The sum of its digit
*/
private static func sumDigits(addend : Int, modulo : Int) -> Int {
return (addend / modulo) + (addend % modulo)
}
/**
Calculate the luhn sum
- Parameter s: The string
- Parameter sohuldDouble: A closure to know if the value should be doubled or not
- Returns: The Luhn sum
*/
private static func calculateLuhnSum(s : String, modulo : Int, shouldDouble : (index : Int) -> Bool) -> Int {
return [Character](s.characters)
.reverse()
.map { codePointFromCharacter($0, modulo: Int32(modulo)) }
.enumerate()
.map { sumDigits($1 * ((shouldDouble(index: $0)) ? 2 : 1), modulo : modulo) }
.reduce(0, combine: +)
}
/**
Calculate the check character
- Parameter input: The input to string from which the check character should be calculated
- Returns: The check character
*/
private static func generateCheckCharacter(input : String, modulo : Int) -> String {
let sum = calculateLuhnSum(input, modulo : modulo, shouldDouble: { ($0 % 2 == 0) })
let remainder = sum % modulo
let checkCodePoint = (modulo - remainder) % modulo
return characterFromCodePoint(checkCodePoint, modulo : modulo)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment