Visit the isbn-verifier exercise on Exercism to read the full instructions and download the exercise files.
Dig Deeper
map with sum
map() with sum()
import java.util.stream.IntStream;
class IsbnVerifier {
public boolean isValid(String s) {
String scrubbed = s.replace("-", "");
return scrubbed.matches("^([0-9]{10}|[0-9]{9}X)$") &&
IntStream.range(0, scrubbed.length())
.map(pos -> {
var chr = scrubbed.charAt(pos);
return (chr != 'X' ? chr - '0' : 10) * (10 - pos);
})
.sum() % 11 == 0;
}
}
This variant of scrubbing and validating the data first starts by importing from packages for what is needed.
The isValid() method starts by calling replace() to scrub out any dash characters.
The matches() method is then called on the scrubbed string and is passed a pattern
which checks for a string of exactly ten digits or nine digits ending in an X.
If the string doesn’t match the pattern, then the && operator short circuits and returns false from the function.
If the string matches the pattern, then the IntStream range() method is called to generate
numbers from 0 up to but not including the length of the scrubbed string.
Each number is passed into a lambda which gets the character in the string at the position of the number.
If the character is not X then it is a digit whose value is determined by subtracting the ASCII value
of 0 from its own ASCII value.
So, if the character is a 0, then subtracting the ASCII value of 0 from itself results in 0.
If the character is a 1, then subtracting the ASCII value of 0 from the ASCII value of 1 results in 1,
and so on.
Another way to convert the character to a digit is to use the built-in
[`Character.digit()`](https://docs.oracle.com/javase/7/docs/api/java/lang/Character.html#digit(char,%20int))
method, which also works for [Unicode](https://docs.oracle.com/javase/tutorial/i18n/text/unicode.html) digits.
Since for this exercise all of the digits are ASCII, simple ASCII math will do.
The digit is multiplied by 10 minus the position, with the position starting at 0.
So, for the position furthest to the left, the digit is multiplied by 10 - 0 (10).
For the position furthest to the right of a string whose length is 10, the digit is multiplied by 10 - 9 (1).
Each mapped value is passed into the sum() method to be added up.
The function returns whether the sum() is evenly divisible by 11 or not.
for-each loop
for-each loop
class IsbnVerifier {
boolean isValid(String stringToVerify) {
int sum = 0;
int pos = 0;
for (var chr: stringToVerify.toCharArray()) {
if (Character.isDigit(chr)) {
sum += (chr - '0') * (10 - pos);
pos++;
continue;
}
if (chr == '-')
continue;
if (chr == 'X' && pos == 9) {
sum += 10;
pos++;
continue;
}
return false;
}
return pos == 10 && sum % 11 == 0;
}
}
This variant of validating as you go begins with initializing its sum and pos variables to 0.
A for-each loop is used to iterate the characters of the string to be verified.
A series of if statements begins by checking if the char is a digit, which is the most likely condition.
If so, it gets the digit value of the char by subtracting the ASCII value of 0 from its own ASCII value.
So, if the char is a 0, then subtracting the ASCII value of 0 from itself results in 0.
If the char is a 1, then subtracting the ASCII value of 0 from the ASCII value of 1 results in 1, and so on.
Another way to convert the `char` to a digit is to use the built-in
[`Character.digit()`](https://docs.oracle.com/javase/8/docs/api/java/lang/Character.html#isDigit-char-)
method, which also works for [Unicode](https://docs.oracle.com/javase/tutorial/i18n/text/unicode.html) `char`s.
Since for this exercise all of the `chars` are ASCII, simple ASCII math will do.
The digit is multiplied by 10 minus the position, with the position starting at 0.
So, for the position furthest to the left, the digit is multiplied by 10 - 0 (10).
For the position furthest to the right of a string whose length is 10, the digit is multiplied by 10 - 9 (1).
The result of the multiplication is added to the sum variable.
The pos variable is incremented and the loop continues.
If the char is not a digit but a dash, the loop continues without incrementing the pos variable.
If the char is an X and the position is 9 (position 9 being at the end of a string whose length is 10),
then 10 is added to the sum, pos is incremented, and the loop continues.
If none of those legal conditions apply, then the char is illegal and the function immediately returns false.
After the for-each is done, the pos variable is checked if the position is 10.
If not, then the && operator short circuits and returns false from the function.
If it is true, the function then returns if the sum is evenly divisible by 11.
chars with forEach
chars() with forEach()
class IsbnVerifier {
boolean isValid(String stringToVerify) {
var acc = new IsbnAcc();
stringToVerify.chars().forEach(acc::calc);
return acc.isIsbn();
}
}
class IsbnAcc {
int sum = 0;
int pos = 0;
boolean allValidChars = true;
boolean isIsbn() {
return allValidChars && pos == 10 && sum % 11 == 0;
}
void calc(int codePoint) {
if (Character.isDigit(codePoint)) {
sum += (codePoint - '0') * (10 - pos);
pos++;
return;
}
if (codePoint == '-')
return;
if (codePoint == 'X' && pos == 9) {
sum += 10;
pos++;
return;
}
allValidChars = false;
}
}
This variant of validating as you go begins with instantiating an object which does the work of validating the letters and accumulating
their values for the ISBN string to be verified.
The object initializes its sum and pos variables to 0, and its allValidChars variable to true.
The chars() method is then called on the string.
Each Character is a Unicode codepoint that is passed into the forEach()method.
The forEach method passes the codepoint into the calc() method of the validating/accumulating object.
The calc() method starts by checking if the codepoint is a digit, which is the most likely condition.
If so, it gets the digit value of the codepoint by subtracting the ASCII value of 0 from its own ASCII value.
So, if the codepoint is a 0, then subtracting the ASCII value of 0 from itself results in 0.
If the codepoint is a 1, then subtracting the ASCII value of 0 from the ASCII value of 1 results in 1, and so on.
Another way to convert the codepoint to a digit is to use the built-in
[`Character.digit()`](https://docs.oracle.com/javase/7/docs/api/java/lang/Character.html#digit(char,%20int))
method, which also works for [Unicode](https://docs.oracle.com/javase/tutorial/i18n/text/unicode.html) codepoints.
Since for this exercise all of the codepoints are ASCII, simple ASCII math will do.
The digit is multiplied by 10 minus the position, with the position starting at 0.
So, for the position furthest to the left, the digit is multiplied by 10 - 0 (10).
For the position furthest to the right of a string whose length is 10, the digit is multiplied by 10 - 9 (1).
The result of the multiplication is added to the sum variable.
The pos variable is incremented and the function returns.
If the codepoint is not a digit but a dash, the function returns without incrementing the pos variable.
If the codepoint is an X and the position is 9 (position 9 being at the end of a string whose length is 10),
then 10 is added to the sum, pos is incremented, and the function returns.
If none of those legal conditions apply, then the codepoint is illegal and allValidChars is set to false.
After the forEach() is done, the isValid() function returns a call to the object’s isIsbn() method.
The isIsbn() method first checks if all chars are valid.
If not, then the && operator short circuits and returns false from the function.
If it is true, it then checks if the position is 10.
If not, then the && operator short circuits and returns false from the function.
If true, then the last check is if the sum is evenly divisible by 11.
If all of the checks are true, the function returns true.
View Community Solutions
The following are the top 3 community solutions by stars:
Source: Exercism java/isbn-verifier