 # paulfioravanti's solution

## to Luhn in the Elixir Track

Published at Aug 10 2019 · 0 comments
Instructions
Test suite
Solution

Given a number determine whether or not it is valid per the Luhn formula.

The Luhn algorithm is a simple checksum formula used to validate a variety of identification numbers, such as credit card numbers and Canadian Social Insurance Numbers.

The task is to check if a given string is valid.

## Validating a Number

Strings of length 1 or less are not valid. Spaces are allowed in the input, but they should be stripped before checking. All other non-digit characters are disallowed.

## Example 1: valid credit card number

``````4539 1488 0343 6467
``````

The first step of the Luhn algorithm is to double every second digit, starting from the right. We will be doubling

``````4_3_ 1_8_ 0_4_ 6_6_
``````

If doubling the number results in a number greater than 9 then subtract 9 from the product. The results of our doubling:

``````8569 2478 0383 3437
``````

Then sum all of the digits:

``````8+5+6+9+2+4+7+8+0+3+8+3+3+4+3+7 = 80
``````

If the sum is evenly divisible by 10, then the number is valid. This number is valid!

## Example 2: invalid credit card number

``````8273 1232 7352 0569
``````

Double the second digits, starting from the right

``````7253 2262 5312 0539
``````

Sum the digits

``````7+2+5+3+2+2+6+2+5+3+1+2+0+5+3+9 = 57
``````

57 is not evenly divisible by 10, so this number is not valid.

## Running tests

Execute the tests with:

``````\$ mix test
``````

### Pending tests

In the test suites, all but the first test have been skipped.

Once you get a test passing, you can unskip the next one by commenting out the relevant `@tag :pending` with a `#` symbol.

For example:

``````# @tag :pending
test "shouting" do
assert Bob.hey("WATCH OUT!") == "Whoa, chill out!"
end
``````

Or, you can enable all the tests by commenting out the `ExUnit.configure` line in the test suite.

``````# ExUnit.configure exclude: :pending, trace: true
``````

If you're stuck on something, it may help to look at some of the available resources out there where answers might be found.

## Source

The Luhn Algorithm on Wikipedia http://en.wikipedia.org/wiki/Luhn_algorithm

## Submitting Incomplete Solutions

It's possible to submit an incomplete solution so you can see how others have completed the exercise.

### luhn_test.exs

``````defmodule LuhnTest do
use ExUnit.Case

test "single digit strings can not be valid" do
refute Luhn.valid?("1")
end

@tag :pending
test "A single zero is invalid" do
refute Luhn.valid?("0")
end

@tag :pending
test "a simple valid SIN that remains valid if reversed" do
assert Luhn.valid?("059")
end

@tag :pending
test "a simple valid SIN that becomes invalid if reversed" do
assert Luhn.valid?("59")
end

@tag :pending
test "a valid Canadian SIN" do
assert Luhn.valid?("055 444 285")
end

@tag :pending
refute Luhn.valid?("055 444 286")
end

@tag :pending
test "invalid credit card" do
refute Luhn.valid?("8273 1232 7352 0569")
end

@tag :pending
test "valid strings with a non-digit included become invalid" do
refute Luhn.valid?("055a 444 285")
end

@tag :pending
test "valid strings with punctuation included become invalid" do
refute Luhn.valid?("055-444-285")
end

@tag :pending
test "valid strings with symbols included become invalid" do
refute Luhn.valid?("055£ 444\$ 285")
end

@tag :pending
test "single zero with space is invalid" do
refute Luhn.valid?(" 0")
end

@tag :pending
test "more than a single zero is valid" do
assert Luhn.valid?("0000 0")
end

@tag :pending
test "input digit 9 is correctly converted to output digit 9" do
assert Luhn.valid?("091")
end
end``````

### test_helper.exs

``````ExUnit.start()
ExUnit.configure(exclude: :pending, trace: true)``````
``````defmodule Luhn do
@two_or_more_digits_only ~r/\A\d{2,}\z/

@doc """
Checks if the given number is valid via the luhn formula
"""
@spec valid?(String.t()) :: boolean
def valid?(number) do
number = String.replace(number, " ", "")
valid_format?(number) and valid_value?(number)
end

defp valid_format?(number) do
String.match?(number, @two_or_more_digits_only)
end

defp valid_value?(number) do
number
|> to_reversed_numbers()
|> Enum.chunk_every(2)
|> evenly_divisible_by_10?()
end

defp to_reversed_numbers(number) do
number
|> String.reverse()
|> String.codepoints()
|> Enum.map(&String.to_integer/1)
end

right_luhn_value =
right_value
|> double()
|> subtract_9_if_greater_than_9()

acc + left_value + right_luhn_value
end

defp add_luhn_value([left_value], acc), do: acc + left_value

defp double(number), do: number * 2

defp subtract_9_if_greater_than_9(number) do
if number > 9, do: number - 9, else: number
end

defp evenly_divisible_by_10?(number), do: rem(number, 10) == 0
end``````