forked from exercism/r
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Update luhn exercise to address changes referred to in exercism#27
- Loading branch information
1 parent
c79dbbb
commit 100110b
Showing
3 changed files
with
65 additions
and
110 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,35 +1,23 @@ | ||
# Convert a number to a valid number, if it isn't already. | ||
luhn <- function(input) { | ||
if (is_valid(input)) { | ||
return(input) | ||
} | ||
diff <- (10 - checksum(10*input)) %% 10 | ||
return(10 * input + diff) | ||
} | ||
|
||
# Get the check digit | ||
check_digit <- function(input) { | ||
as.numeric(substring(input,nchar(input))) | ||
} | ||
|
||
# Compute the checksum | ||
checksum <- function(input) { | ||
sum(addends(input)) | ||
} | ||
|
||
# Determine whether the number is valid. | ||
is_valid <- function(input) { | ||
checksum(input) %% 10 == 0 | ||
} | ||
|
||
# addends returns the vector of numbers that follow the luhn algorithm | ||
addends <- function(input) { | ||
check <- check_digit(input) | ||
v <- as.numeric(strsplit(as.character(input),"")[[1]]) | ||
# counting from right double value of every second digit | ||
start_seq <- ifelse(length(v) %% 2 == 1, 0, 1) | ||
v2 <- replace(v,seq(start_seq,length(v),2),v[seq(start_seq,length(v),2)]*2) | ||
v2 <- ifelse(v2 > 9, v2 - 9, v2) | ||
replace_vals <- v2[seq(start_seq,length(v),2)] | ||
replace(v,seq(start_seq,length(v),2),replace_vals) | ||
|
||
# Strip spaces, check length & check for invalid characters | ||
input_vector <- strsplit(gsub(pattern = " ", replacement = "", input), "")[[1]] | ||
if (length(input_vector) < 2 || any(grepl("[^[:digit:]]", input_vector))) { | ||
return (FALSE) | ||
} | ||
|
||
# Convert to numeric | ||
num_vector <- as.numeric(input_vector) | ||
|
||
# Double every second digit starting from the right | ||
num_vector <- rev(num_vector) | ||
num_vector[seq(2,length(num_vector),2)] = num_vector[seq(2,length(num_vector),2)]*2 | ||
|
||
# Subtract 9 if > 9 (can apply to all since no digit can be greater than 9 before doubling) | ||
num_vector <- ifelse(num_vector > 9, num_vector - 9, num_vector) | ||
|
||
# Check checksum is divisible by 10 | ||
sum(num_vector) %% 10 == 0 | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,25 +1,4 @@ | ||
# Convert a number to a valid number, if it isn't already. | ||
luhn <- function() { | ||
|
||
} | ||
|
||
# Get the check digit | ||
check_digit <- function() { | ||
|
||
} | ||
|
||
# Compute the checksum | ||
checksum <- function() { | ||
|
||
} | ||
|
||
# Determine whether the number is valid. | ||
is_valid <- function() { | ||
is_valid <- function(input) { | ||
|
||
} | ||
|
||
|
||
# addends returns the vector of numbers that follow the luhn algorithm | ||
addends <- function(input) { | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,81 +1,69 @@ | ||
source('./luhn.R') | ||
source('./is_valid.R') | ||
suppressPackageStartupMessages({ require(testthat) }) | ||
|
||
test_that("check digit", { | ||
input <- 34567 | ||
expect_equal(check_digit(input), | ||
7 | ||
) | ||
test_that("single digit strings can not be valid", { | ||
input <- "1" | ||
expect_equal(is_valid(input), FALSE) | ||
}) | ||
|
||
test_that("check digit with input ending in zero", { | ||
input <- 91370 | ||
expect_equal(check_digit(input), | ||
0 | ||
) | ||
test_that("A single zero is invalid", { | ||
input <- "0" | ||
expect_equal(is_valid(input), FALSE) | ||
}) | ||
|
||
test_that("check addends", { | ||
input <- 12121 | ||
expect_equal(addends(input), | ||
c(1,4,1,4,1) | ||
) | ||
test_that("simple valid sin", { | ||
input <- " 5 9 " | ||
expect_equal(is_valid(input), TRUE) | ||
}) | ||
|
||
test_that("check too large addends", { | ||
input <- 8631 | ||
expect_equal(addends(input), | ||
c(7,6,6,1) | ||
) | ||
test_that("valid Canadian SIN", { | ||
input <- "046 454 286" | ||
expect_equal(is_valid(input), TRUE) | ||
}) | ||
|
||
test_that("checksum", { | ||
input <- 4913 | ||
expect_equal(checksum(input), | ||
22 | ||
) | ||
test_that("invalid Canadian SIN", { | ||
input <- "046 454 287" | ||
expect_equal(is_valid(input), FALSE) | ||
}) | ||
|
||
test_that("checksum of larger number", { | ||
input <- 201773 | ||
expect_equal(checksum(input), | ||
21 | ||
) | ||
test_that("invalid credit card", { | ||
input <- "8273 1232 7352 0569" | ||
expect_equal(is_valid(input), FALSE) | ||
}) | ||
|
||
test_that("check invalid number", { | ||
input <- 738 | ||
expect_equal(is_valid(input), | ||
FALSE | ||
) | ||
test_that("valid strings with a non-digit added become invalid", { | ||
input <- "046a 454 286" | ||
expect_equal(is_valid(input), FALSE) | ||
}) | ||
|
||
test_that("check valid number", { | ||
input <- 8739567 | ||
expect_equal(is_valid(input), | ||
TRUE | ||
) | ||
test_that("punctuation is not allowed", { | ||
input <- "055-444-285" | ||
expect_equal(is_valid(input), FALSE) | ||
}) | ||
|
||
test_that("create valid number", { | ||
input <- 123 | ||
expect_equal(luhn(input), | ||
1230 | ||
) | ||
test_that("symbols are not allowed", { | ||
input <- "055£ 444$ 285" | ||
expect_equal(is_valid(input), FALSE) | ||
}) | ||
|
||
test_that("create larger valid number", { | ||
input <- 873956 | ||
expect_equal(luhn(input), | ||
8739567 | ||
) | ||
test_that("single zero with space is invalid", { | ||
input <- " 0" | ||
expect_equal(is_valid(input), FALSE) | ||
}) | ||
|
||
test_that("create even larger valid number", { | ||
input <- 837263756 | ||
expect_equal(luhn(input), | ||
8372637564 | ||
) | ||
test_that("lots of zeros are valid", { | ||
input <- " 00000" | ||
expect_equal(is_valid(input), TRUE) | ||
}) | ||
|
||
test_that("another valid sin", { | ||
input <- "055 444 285" | ||
expect_equal(is_valid(input), TRUE) | ||
}) | ||
|
||
test_that("nine doubled is nine", { | ||
input <- "091" | ||
expect_equal(is_valid(input), TRUE) | ||
}) | ||
|
||
print("All tests passed!") |
100110b
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. There's only one comment I had, where I think the test suite might sourcing a non-existent file. Otherwise, everything looks good.
BTW, is the test-running process working well for you? I haven't been using R for years, and don't know if there are now better ways of running unit tests in RStudio or command line, than what I wrote in the documentation.
100110b
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes - whoops. Thanks for spotting that. Had that
source()
statement commented out while I was testing and accidentally overwrote it with a find & replace. Will fix that now.I think the testing process is great as is.
testthat
is very commonly used and I don't see any problems with the way we have things currently. There are a few other potential approaches (RUnit, testit etc) but I think what we've got is the most standard. All it requires is opening thetest_*.R
script in RStudio and hitting Source, which is super simple and should be learned early on by beginners. One can also automate the tests with testthat'sauto_test()
, which will then re-run tests whenever the main script is edited (& saved), but this isn't really necessary.