Solved luhn in Bash

This commit is contained in:
David Doblas Jiménez 2023-05-07 16:46:55 +02:00
parent b6a9d83cf3
commit 8c3377d92a
7 changed files with 1073 additions and 0 deletions

View File

@ -0,0 +1,28 @@
{
"authors": [
"sjwarner-bp"
],
"contributors": [
"bkhl",
"budmc29",
"glennj",
"guygastineau",
"IsaacG",
"kotp",
"ZapAnton"
],
"files": {
"solution": [
"luhn.sh"
],
"test": [
"luhn.bats"
],
"example": [
".meta/example.sh"
]
},
"blurb": "Given a number determine whether or not it is valid per the Luhn formula.",
"source": "The Luhn Algorithm on Wikipedia",
"source_url": "https://en.wikipedia.org/wiki/Luhn_algorithm"
}

View File

@ -0,0 +1 @@
{"track":"bash","exercise":"luhn","id":"83c36e7ba93042ac88d7d5adb58821ae","url":"https://exercism.org/tracks/bash/exercises/luhn","handle":"Kimawari","is_requester":true,"auto_approve":false}

105
bash/luhn/HELP.md Normal file
View File

@ -0,0 +1,105 @@
# Help
## Running the tests
Each exercise contains a test file.
Run the tests using the `bats` program.
```bash
bats hello_world.bats
```
`bats` will need to be installed.
See the [Testing on the Bash track][tests] page for instructions to install `bats` for your system.
[tests]: https://exercism.org/docs/tracks/bash/tests
## Help for assert functions
The tests use functions from the [bats-assert][bats-assert] library.
Help for the various `assert*` functions can be found there.
[bats-assert]: https://github.com/bats-core/bats-assert
## Skipped tests
Solving an exercise means making all its tests pass.
By default, only one test (the first one) is executed when you run the tests.
This is intentional, as it allows you to focus on just making that one test pass.
Once it passes, you can enable the next test by commenting out or removing the next annotation:
```bash
[[ $BATS_RUN_SKIPPED == true ]] || skip
```
## Overriding skips
To run all tests, including the ones with `skip` annotations, you can run:
```bash
BATS_RUN_SKIPPED=true bats exercise_name.bats
```
It can be convenient to use a wrapper function to save on typing:
```bash
bats() {
BATS_RUN_SKIPPED=true command bats *.bats
}
```
Then run tests with just:
```bash
bats
```
## Submitting your solution
You can submit your solution using the `exercism submit luhn.sh` command.
This command will upload your solution to the Exercism website and print the solution page's URL.
It's possible to submit an incomplete solution which allows you to:
- See how others have completed the exercise
- Request help from a mentor
## Need to get help?
If you'd like help solving the exercise, check the following pages:
- The [Bash track's documentation](https://exercism.org/docs/tracks/bash)
- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5)
- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs)
Should those resources not suffice, you could submit your (incomplete) solution to request mentoring.
Check your code for syntax errors: paste your code into
[https://shellcheck.net](https://shellcheck.net) (or [install it](https://github.com/koalaman/shellcheck#user-content-installing) on your machine).
Stack Overflow will be your first stop for bash questions.
* start with the [`bash` tag](https://stackoverflow.com/questions/tagged/bash) to search for your specific question: it's probably already been asked
* under the bash tag on Stackoverflow, the [Learn more...](https://stackoverflow.com/tags/bash/info) link has _tons_ of good information.
* the "Books and Resources" section is particularly useful.
* the [`bash` tag](https://unix.stackexchange.com/questions/tagged/bash) on Unix & Linux is also active
## External utilities
`bash` is a language to write "scripts" -- programs that can call
external tools, such as
[`sed`](https://www.gnu.org/software/sed/),
[`awk`](https://www.gnu.org/software/gawk/),
[`date`](https://www.gnu.org/software/coreutils/manual/html_node/date-invocation.html)
and even programs written in other programming languages,
like [`Python`](https://www.python.org/).
This track does not restrict the usage of these utilities, and as long
as your solution is portable between systems and does not require
installation of third party applications, feel free to use them to solve
the exercise.
For an extra challenge, if you would like to have a better understanding of
the language, try to re-implement the solution in pure bash, without using
any external tools. There are some types of problems that bash cannot solve,
such as floating point arithmetic and manipulating dates: for those, you
must call out to an external tool.

89
bash/luhn/README.md Normal file
View File

@ -0,0 +1,89 @@
# Luhn
Welcome to Luhn on Exercism's Bash Track.
If you need help running the tests or submitting your code, check out `HELP.md`.
## Instructions
Given a number determine whether or not it is valid per the Luhn formula.
The [Luhn algorithm][luhn] 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
```text
4539 3195 0343 6467
```
The first step of the Luhn algorithm is to double every second digit, starting from the right.
We will be doubling
```text
4_3_ 3_9_ 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:
```text
8569 6195 0383 3437
```
Then sum all of the digits:
```text
8+5+6+9+6+1+9+5+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
```text
8273 1232 7352 0569
```
Double the second digits, starting from the right
```text
7253 2262 5312 0539
```
Sum the digits
```text
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.
[luhn]: https://en.wikipedia.org/wiki/Luhn_algorithm
## Source
### Created by
- @sjwarner-bp
### Contributed to by
- @bkhl
- @budmc29
- @glennj
- @guygastineau
- @IsaacG
- @kotp
- @ZapAnton
### Based on
The Luhn Algorithm on Wikipedia - https://en.wikipedia.org/wiki/Luhn_algorithm

637
bash/luhn/bats-extra.bash Normal file
View File

@ -0,0 +1,637 @@
# This is the source code for bats-support and bats-assert, concatenated
# * https://github.com/bats-core/bats-support
# * https://github.com/bats-core/bats-assert
#
# Comments have been removed to save space. See the git repos for full source code.
############################################################
#
# bats-support - Supporting library for Bats test helpers
#
# Written in 2016 by Zoltan Tombol <zoltan dot tombol at gmail dot com>
#
# To the extent possible under law, the author(s) have dedicated all
# copyright and related and neighboring rights to this software to the
# public domain worldwide. This software is distributed without any
# warranty.
#
# You should have received a copy of the CC0 Public Domain Dedication
# along with this software. If not, see
# <http://creativecommons.org/publicdomain/zero/1.0/>.
#
fail() {
(( $# == 0 )) && batslib_err || batslib_err "$@"
return 1
}
batslib_is_caller() {
local -i is_mode_direct=1
# Handle options.
while (( $# > 0 )); do
case "$1" in
-i|--indirect) is_mode_direct=0; shift ;;
--) shift; break ;;
*) break ;;
esac
done
# Arguments.
local -r func="$1"
# Check call stack.
if (( is_mode_direct )); then
[[ $func == "${FUNCNAME[2]}" ]] && return 0
else
local -i depth
for (( depth=2; depth<${#FUNCNAME[@]}; ++depth )); do
[[ $func == "${FUNCNAME[$depth]}" ]] && return 0
done
fi
return 1
}
batslib_err() {
{ if (( $# > 0 )); then
echo "$@"
else
cat -
fi
} >&2
}
batslib_count_lines() {
local -i n_lines=0
local line
while IFS='' read -r line || [[ -n $line ]]; do
(( ++n_lines ))
done < <(printf '%s' "$1")
echo "$n_lines"
}
batslib_is_single_line() {
for string in "$@"; do
(( $(batslib_count_lines "$string") > 1 )) && return 1
done
return 0
}
batslib_get_max_single_line_key_width() {
local -i max_len=-1
while (( $# != 0 )); do
local -i key_len="${#1}"
batslib_is_single_line "$2" && (( key_len > max_len )) && max_len="$key_len"
shift 2
done
echo "$max_len"
}
batslib_print_kv_single() {
local -ir col_width="$1"; shift
while (( $# != 0 )); do
printf '%-*s : %s\n' "$col_width" "$1" "$2"
shift 2
done
}
batslib_print_kv_multi() {
while (( $# != 0 )); do
printf '%s (%d lines):\n' "$1" "$( batslib_count_lines "$2" )"
printf '%s\n' "$2"
shift 2
done
}
batslib_print_kv_single_or_multi() {
local -ir width="$1"; shift
local -a pairs=( "$@" )
local -a values=()
local -i i
for (( i=1; i < ${#pairs[@]}; i+=2 )); do
values+=( "${pairs[$i]}" )
done
if batslib_is_single_line "${values[@]}"; then
batslib_print_kv_single "$width" "${pairs[@]}"
else
local -i i
for (( i=1; i < ${#pairs[@]}; i+=2 )); do
pairs[$i]="$( batslib_prefix < <(printf '%s' "${pairs[$i]}") )"
done
batslib_print_kv_multi "${pairs[@]}"
fi
}
batslib_prefix() {
local -r prefix="${1:- }"
local line
while IFS='' read -r line || [[ -n $line ]]; do
printf '%s%s\n' "$prefix" "$line"
done
}
batslib_mark() {
local -r symbol="$1"; shift
# Sort line numbers.
set -- $( sort -nu <<< "$( printf '%d\n' "$@" )" )
local line
local -i idx=0
while IFS='' read -r line || [[ -n $line ]]; do
if (( ${1:--1} == idx )); then
printf '%s\n' "${symbol}${line:${#symbol}}"
shift
else
printf '%s\n' "$line"
fi
(( ++idx ))
done
}
batslib_decorate() {
echo
echo "-- $1 --"
cat -
echo '--'
echo
}
############################################################
assert() {
if ! "$@"; then
batslib_print_kv_single 10 'expression' "$*" \
| batslib_decorate 'assertion failed' \
| fail
fi
}
assert_equal() {
if [[ $1 != "$2" ]]; then
batslib_print_kv_single_or_multi 8 \
'expected' "$2" \
'actual' "$1" \
| batslib_decorate 'values do not equal' \
| fail
fi
}
assert_failure() {
: "${output?}"
: "${status?}"
(( $# > 0 )) && local -r expected="$1"
if (( status == 0 )); then
batslib_print_kv_single_or_multi 6 'output' "$output" \
| batslib_decorate 'command succeeded, but it was expected to fail' \
| fail
elif (( $# > 0 )) && (( status != expected )); then
{ local -ir width=8
batslib_print_kv_single "$width" \
'expected' "$expected" \
'actual' "$status"
batslib_print_kv_single_or_multi "$width" \
'output' "$output"
} \
| batslib_decorate 'command failed as expected, but status differs' \
| fail
fi
}
assert_line() {
local -i is_match_line=0
local -i is_mode_partial=0
local -i is_mode_regexp=0
: "${lines?}"
# Handle options.
while (( $# > 0 )); do
case "$1" in
-n|--index)
if (( $# < 2 )) || ! [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then
echo "\`--index' requires an integer argument: \`$2'" \
| batslib_decorate 'ERROR: assert_line' \
| fail
return $?
fi
is_match_line=1
local -ri idx="$2"
shift 2
;;
-p|--partial) is_mode_partial=1; shift ;;
-e|--regexp) is_mode_regexp=1; shift ;;
--) shift; break ;;
*) break ;;
esac
done
if (( is_mode_partial )) && (( is_mode_regexp )); then
echo "\`--partial' and \`--regexp' are mutually exclusive" \
| batslib_decorate 'ERROR: assert_line' \
| fail
return $?
fi
# Arguments.
local -r expected="$1"
if (( is_mode_regexp == 1 )) && [[ '' =~ $expected ]] || (( $? == 2 )); then
echo "Invalid extended regular expression: \`$expected'" \
| batslib_decorate 'ERROR: assert_line' \
| fail
return $?
fi
# Matching.
if (( is_match_line )); then
# Specific line.
if (( is_mode_regexp )); then
if ! [[ ${lines[$idx]} =~ $expected ]]; then
batslib_print_kv_single 6 \
'index' "$idx" \
'regexp' "$expected" \
'line' "${lines[$idx]}" \
| batslib_decorate 'regular expression does not match line' \
| fail
fi
elif (( is_mode_partial )); then
if [[ ${lines[$idx]} != *"$expected"* ]]; then
batslib_print_kv_single 9 \
'index' "$idx" \
'substring' "$expected" \
'line' "${lines[$idx]}" \
| batslib_decorate 'line does not contain substring' \
| fail
fi
else
if [[ ${lines[$idx]} != "$expected" ]]; then
batslib_print_kv_single 8 \
'index' "$idx" \
'expected' "$expected" \
'actual' "${lines[$idx]}" \
| batslib_decorate 'line differs' \
| fail
fi
fi
else
# Contained in output.
if (( is_mode_regexp )); then
local -i idx
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
[[ ${lines[$idx]} =~ $expected ]] && return 0
done
{ local -ar single=( 'regexp' "$expected" )
local -ar may_be_multi=( 'output' "$output" )
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
batslib_print_kv_single "$width" "${single[@]}"
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
} \
| batslib_decorate 'no output line matches regular expression' \
| fail
elif (( is_mode_partial )); then
local -i idx
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
[[ ${lines[$idx]} == *"$expected"* ]] && return 0
done
{ local -ar single=( 'substring' "$expected" )
local -ar may_be_multi=( 'output' "$output" )
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
batslib_print_kv_single "$width" "${single[@]}"
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
} \
| batslib_decorate 'no output line contains substring' \
| fail
else
local -i idx
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
[[ ${lines[$idx]} == "$expected" ]] && return 0
done
{ local -ar single=( 'line' "$expected" )
local -ar may_be_multi=( 'output' "$output" )
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
batslib_print_kv_single "$width" "${single[@]}"
batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
} \
| batslib_decorate 'output does not contain line' \
| fail
fi
fi
}
assert_output() {
local -i is_mode_partial=0
local -i is_mode_regexp=0
local -i is_mode_nonempty=0
local -i use_stdin=0
: "${output?}"
# Handle options.
if (( $# == 0 )); then
is_mode_nonempty=1
fi
while (( $# > 0 )); do
case "$1" in
-p|--partial) is_mode_partial=1; shift ;;
-e|--regexp) is_mode_regexp=1; shift ;;
-|--stdin) use_stdin=1; shift ;;
--) shift; break ;;
*) break ;;
esac
done
if (( is_mode_partial )) && (( is_mode_regexp )); then
echo "\`--partial' and \`--regexp' are mutually exclusive" \
| batslib_decorate 'ERROR: assert_output' \
| fail
return $?
fi
# Arguments.
local expected
if (( use_stdin )); then
expected="$(cat -)"
else
expected="${1-}"
fi
# Matching.
if (( is_mode_nonempty )); then
if [ -z "$output" ]; then
echo 'expected non-empty output, but output was empty' \
| batslib_decorate 'no output' \
| fail
fi
elif (( is_mode_regexp )); then
if [[ '' =~ $expected ]] || (( $? == 2 )); then
echo "Invalid extended regular expression: \`$expected'" \
| batslib_decorate 'ERROR: assert_output' \
| fail
elif ! [[ $output =~ $expected ]]; then
batslib_print_kv_single_or_multi 6 \
'regexp' "$expected" \
'output' "$output" \
| batslib_decorate 'regular expression does not match output' \
| fail
fi
elif (( is_mode_partial )); then
if [[ $output != *"$expected"* ]]; then
batslib_print_kv_single_or_multi 9 \
'substring' "$expected" \
'output' "$output" \
| batslib_decorate 'output does not contain substring' \
| fail
fi
else
if [[ $output != "$expected" ]]; then
batslib_print_kv_single_or_multi 8 \
'expected' "$expected" \
'actual' "$output" \
| batslib_decorate 'output differs' \
| fail
fi
fi
}
assert_success() {
: "${output?}"
: "${status?}"
if (( status != 0 )); then
{ local -ir width=6
batslib_print_kv_single "$width" 'status' "$status"
batslib_print_kv_single_or_multi "$width" 'output' "$output"
} \
| batslib_decorate 'command failed' \
| fail
fi
}
refute() {
if "$@"; then
batslib_print_kv_single 10 'expression' "$*" \
| batslib_decorate 'assertion succeeded, but it was expected to fail' \
| fail
fi
}
refute_line() {
local -i is_match_line=0
local -i is_mode_partial=0
local -i is_mode_regexp=0
: "${lines?}"
# Handle options.
while (( $# > 0 )); do
case "$1" in
-n|--index)
if (( $# < 2 )) || ! [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then
echo "\`--index' requires an integer argument: \`$2'" \
| batslib_decorate 'ERROR: refute_line' \
| fail
return $?
fi
is_match_line=1
local -ri idx="$2"
shift 2
;;
-p|--partial) is_mode_partial=1; shift ;;
-e|--regexp) is_mode_regexp=1; shift ;;
--) shift; break ;;
*) break ;;
esac
done
if (( is_mode_partial )) && (( is_mode_regexp )); then
echo "\`--partial' and \`--regexp' are mutually exclusive" \
| batslib_decorate 'ERROR: refute_line' \
| fail
return $?
fi
# Arguments.
local -r unexpected="$1"
if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then
echo "Invalid extended regular expression: \`$unexpected'" \
| batslib_decorate 'ERROR: refute_line' \
| fail
return $?
fi
# Matching.
if (( is_match_line )); then
# Specific line.
if (( is_mode_regexp )); then
if [[ ${lines[$idx]} =~ $unexpected ]]; then
batslib_print_kv_single 6 \
'index' "$idx" \
'regexp' "$unexpected" \
'line' "${lines[$idx]}" \
| batslib_decorate 'regular expression should not match line' \
| fail
fi
elif (( is_mode_partial )); then
if [[ ${lines[$idx]} == *"$unexpected"* ]]; then
batslib_print_kv_single 9 \
'index' "$idx" \
'substring' "$unexpected" \
'line' "${lines[$idx]}" \
| batslib_decorate 'line should not contain substring' \
| fail
fi
else
if [[ ${lines[$idx]} == "$unexpected" ]]; then
batslib_print_kv_single 5 \
'index' "$idx" \
'line' "${lines[$idx]}" \
| batslib_decorate 'line should differ' \
| fail
fi
fi
else
# Line contained in output.
if (( is_mode_regexp )); then
local -i idx
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
if [[ ${lines[$idx]} =~ $unexpected ]]; then
{ local -ar single=( 'regexp' "$unexpected" 'index' "$idx" )
local -a may_be_multi=( 'output' "$output" )
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
batslib_print_kv_single "$width" "${single[@]}"
if batslib_is_single_line "${may_be_multi[1]}"; then
batslib_print_kv_single "$width" "${may_be_multi[@]}"
else
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
batslib_print_kv_multi "${may_be_multi[@]}"
fi
} \
| batslib_decorate 'no line should match the regular expression' \
| fail
return $?
fi
done
elif (( is_mode_partial )); then
local -i idx
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
if [[ ${lines[$idx]} == *"$unexpected"* ]]; then
{ local -ar single=( 'substring' "$unexpected" 'index' "$idx" )
local -a may_be_multi=( 'output' "$output" )
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
batslib_print_kv_single "$width" "${single[@]}"
if batslib_is_single_line "${may_be_multi[1]}"; then
batslib_print_kv_single "$width" "${may_be_multi[@]}"
else
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
batslib_print_kv_multi "${may_be_multi[@]}"
fi
} \
| batslib_decorate 'no line should contain substring' \
| fail
return $?
fi
done
else
local -i idx
for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
if [[ ${lines[$idx]} == "$unexpected" ]]; then
{ local -ar single=( 'line' "$unexpected" 'index' "$idx" )
local -a may_be_multi=( 'output' "$output" )
local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )"
batslib_print_kv_single "$width" "${single[@]}"
if batslib_is_single_line "${may_be_multi[1]}"; then
batslib_print_kv_single "$width" "${may_be_multi[@]}"
else
may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )"
batslib_print_kv_multi "${may_be_multi[@]}"
fi
} \
| batslib_decorate 'line should not be in output' \
| fail
return $?
fi
done
fi
fi
}
refute_output() {
local -i is_mode_partial=0
local -i is_mode_regexp=0
local -i is_mode_empty=0
local -i use_stdin=0
: "${output?}"
# Handle options.
if (( $# == 0 )); then
is_mode_empty=1
fi
while (( $# > 0 )); do
case "$1" in
-p|--partial) is_mode_partial=1; shift ;;
-e|--regexp) is_mode_regexp=1; shift ;;
-|--stdin) use_stdin=1; shift ;;
--) shift; break ;;
*) break ;;
esac
done
if (( is_mode_partial )) && (( is_mode_regexp )); then
echo "\`--partial' and \`--regexp' are mutually exclusive" \
| batslib_decorate 'ERROR: refute_output' \
| fail
return $?
fi
# Arguments.
local unexpected
if (( use_stdin )); then
unexpected="$(cat -)"
else
unexpected="${1-}"
fi
if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then
echo "Invalid extended regular expression: \`$unexpected'" \
| batslib_decorate 'ERROR: refute_output' \
| fail
return $?
fi
# Matching.
if (( is_mode_empty )); then
if [ -n "$output" ]; then
batslib_print_kv_single_or_multi 6 \
'output' "$output" \
| batslib_decorate 'output non-empty, but expected no output' \
| fail
fi
elif (( is_mode_regexp )); then
if [[ $output =~ $unexpected ]]; then
batslib_print_kv_single_or_multi 6 \
'regexp' "$unexpected" \
'output' "$output" \
| batslib_decorate 'regular expression should not match output' \
| fail
fi
elif (( is_mode_partial )); then
if [[ $output == *"$unexpected"* ]]; then
batslib_print_kv_single_or_multi 9 \
'substring' "$unexpected" \
'output' "$output" \
| batslib_decorate 'output should not contain substring' \
| fail
fi
else
if [[ $output == "$unexpected" ]]; then
batslib_print_kv_single_or_multi 6 \
'output' "$output" \
| batslib_decorate 'output equals, but it was expected to differ' \
| fail
fi
fi
}

158
bash/luhn/luhn.bats Normal file
View File

@ -0,0 +1,158 @@
#!/usr/bin/env bats
load bats-extra
# local version: 1.7.0.0
@test "single digit strings can not be valid" {
#[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash luhn.sh "1"
assert_success
assert_output "false"
}
@test "a single zero is invalid" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash luhn.sh "0"
assert_success
assert_output "false"
}
@test "a simple valid SIN that remains valid if reversed" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash luhn.sh "059"
assert_success
assert_output "true"
}
@test "a simple valid SIN that becomes invalid if reversed" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash luhn.sh "59"
assert_success
assert_output "true"
}
@test "a valid Canadian SIN" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash luhn.sh "055 444 285"
assert_success
assert_output "true"
}
@test "invalid Canadian SIN" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash luhn.sh "055 444 286"
assert_success
assert_output "false"
}
@test "invalid credit card" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash luhn.sh "8273 1232 7352 0569"
assert_success
assert_output "false"
}
@test "invalid long number with an even remainder" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash luhn.sh "1 2345 6789 1234 5678 9012"
assert_success
assert_output "false"
}
@test "invalid long number with a remainder divisible by 5" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash luhn.sh "1 2345 6789 1234 5678 9013"
assert_success
assert_output "false"
}
@test "valid number with an even number of digits" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash luhn.sh "095 245 88"
assert_success
assert_output "true"
}
@test "valid number with an odd number of spaces" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash luhn.sh "234 567 891 234"
assert_success
assert_output "true"
}
@test "valid strings with a non-digit included become invalid" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash luhn.sh "055a 444 285"
assert_success
assert_output "false"
}
@test "valid strings with punctuation included become invalid" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash luhn.sh "055-444-285"
assert_success
assert_output "false"
}
@test "valid strings with symbols included become invalid" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash luhn.sh "055£ 444$ 285"
assert_success
assert_output "false"
}
@test "single zero with space is invalid" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash luhn.sh " 0"
assert_success
assert_output "false"
}
@test "more than a single zero is valid" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash luhn.sh "0000 0"
assert_success
assert_output "true"
}
@test "input digit 9 is correctly converted to output digit 9" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash luhn.sh "091"
assert_success
assert_output "true"
}
@test "very long input is valid" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash luhn.sh "9999999999 9999999999 9999999999 9999999999"
assert_success
assert_output "true"
}
@test "valid luhn with an odd number of digits and non zero first digit" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash luhn.sh "109"
assert_success
assert_output "true"
}
@test "using ascii value for non-doubled non-digit isn't allowed" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash luhn.sh "055b 444 285"
assert_success
assert_output "false"
}
@test "using ascii value for doubled non-digit isn't allowed" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash luhn.sh ":9"
assert_success
assert_output "false"
}
@test "non-numeric, non-space char in the middle with a sum that's divisible by 10 isn't allowed" {
[[ $BATS_RUN_SKIPPED == "true" ]] || skip
run bash luhn.sh "59%59"
assert_success
assert_output "false"
}

55
bash/luhn/luhn.sh Normal file
View File

@ -0,0 +1,55 @@
#!/usr/bin/env bash
#!/usr/bin/env bash
# Strip all spaces from input
input=$(echo $1 | tr -d ' ')
# Check input length
if [ ${#input} -le 1 ]
then
echo "false"
exit 0
fi
# Check for non-digit characters
if [[ $input =~ [^[:digit:]] ]]
then
echo "false"
exit 0
fi
# Double every second digit from the right
doubled_input=""
for (( i=${#input}-1; i>=0; i-- ))
do
# Checks if the current digit is a second digit
if [ $(((${#input}-i) % 2)) -eq 0 ]
then
doubled_digit=$(( ${input:$i:1} * 2 ))
if [ $doubled_digit -gt 9 ]
then
doubled_digit=$(( doubled_digit - 9 ))
fi
# Adds the new digit to the left of "doubled_input"
doubled_input="${doubled_digit}${doubled_input}"
# If is not a second digit, copy it to the new string.
else
doubled_input="${input:$i:1}${doubled_input}"
fi
done
# Calculate the sum of all digits
sum=0
for (( i=0; i<${#doubled_input}; i++ ))
do
sum=$(( sum + ${doubled_input:$i:1} ))
done
# Check if sum is divisible by 10
if [ $(( sum % 10 )) -eq 0 ]
then
echo "true"
else
echo "false"
fi