Skip to content

Commit

Permalink
Update luhn exercise to address changes referred to in exercism#27
Browse files Browse the repository at this point in the history
  • Loading branch information
jonmcalder committed Feb 10, 2017
1 parent c79dbbb commit 100110b
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 110 deletions.
52 changes: 20 additions & 32 deletions exercises/luhn/example.R
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

}
23 changes: 1 addition & 22 deletions exercises/luhn/luhn.R
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) {

}
100 changes: 44 additions & 56 deletions exercises/luhn/test_luhn.R
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!")

2 comments on commit 100110b

@jonboiser
Copy link

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.

@jonmcalder
Copy link
Owner Author

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 the test_*.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's auto_test(), which will then re-run tests whenever the main script is edited (& saved), but this isn't really necessary.

Please sign in to comment.