Groovy error with method mod or % - groovy

I just started to program in Groovy, I have got this error and I wanted to see if anyone could help me to work it out.
java.lang.UnsupportedOperationException: Cannot use mod() on this number type: java.math.BigDecimal with value: 5
at Script1.hailstone(Script1.groovy:8)
at Script1$hailstone.callCurrent(Unknown Source)
at Script1.hailstone(Script1.groovy:11)
at Script1$hailstone.callCurrent(Unknown Source)
at Script1.hailstone(Script1.groovy:14)
at Script1$_run_closure1.doCall(Script1.groovy:1)
at Script1.run(Script1.groovy:1)
I have the following Groovy code
def list = [1,2,3].findAll{it-> hailstone(it)}
def hailstone(num){
if(num==1){
return 1;
}
println num
println num.mod(2)
if(num.mod(2)==0){
num = num/2;
return 1 + hailstone(num)
}else{
num = 3*num + 1
return 1 + hailstone(num)
}
}
​
​
and the output:
2
0
3
1
10
0
5
and then it suddenly throws an error on 5.mod(2).
Thanks in advance.

Looks like 'num' is getting coerced into a BigDecimal when you hit the line
num = num/2
If you change the signature of the hailstone method to:
def hailstone(int num) it will not crash (because the parameter will get coerced to an int on each invocation), but this may not give the results that you want, as you will lose precision, e.g. when 'num' is an odd number, and num/2 yields a decimal value, the value will be truncated.
For more info on the (sometimes surprising) way that Groovy math operations work, take a look at http://groovy.codehaus.org/Groovy+Math

After running this code, it was not producing the right output because of the findAll. Here is the code with some minor tweaks:
def list = [1,2,3].collect { hailstone(it) } // use collect, no need for the "it ->" variable it is implicit.
def hailstone(int num) { // int data type to prevent BigDecimal from being passed to mod()
if (num == 1) {
return 1 // no need for semi-colons in groovy
} else if (num % 2 == 0) { // use modulus operator
num = num / 2
} else {
num = 3 * num + 1
}
return 1 + hailstone(num) // this line happens regardless of the condition in the else if or the else
}
println list // outputs : [1,2,8]

Related

Eager interpolation with just a closure behaves like a lazy one?

As part of learning Groovy, I'm trying to explore all intricate possibilities provided by string interpolation.
One of my little experiments gave results that don't make sense to me, and now I'm wondering whether I've completely misunderstood the basic concepts of lazy and eager interpolation in Groovy.
Here's the code I ran:
def myVar1 = 3
// An eager interpolation containing just a closure.
def myStr = "${{->myVar1}}"
print ("Just after the creation of myStr\n")
print (myStr as String)
myVar1 += 1 // Bump up myVar1.
print ("\nJust after incrementing myVar1\n")
print (myStr as String)
Here's the output I got:
Just after the creation of myStr
3
Just after incrementing myVar1
4
Clearly, the closure has been invoked a second time. And the only way the closure could have been re-executed is by the containing interpolation getting re-evaluated. But then, the containing interpolation is, by itself, not a closure, though it contains a closure. So then, why is it getting re-evaluated?
This is how GString.toString() method is implemented. If you take a look at the source code of GString class, you will find something like this:
public String toString() {
StringWriter buffer = new StringWriter();
try {
writeTo(buffer);
}
catch (IOException e) {
throw new StringWriterIOException(e);
}
return buffer.toString();
}
public Writer writeTo(Writer out) throws IOException {
String[] s = getStrings();
int numberOfValues = values.length;
for (int i = 0, size = s.length; i < size; i++) {
out.write(s[i]);
if (i < numberOfValues) {
final Object value = values[i];
if (value instanceof Closure) {
final Closure c = (Closure) value;
if (c.getMaximumNumberOfParameters() == 0) {
InvokerHelper.write(out, c.call());
} else if (c.getMaximumNumberOfParameters() == 1) {
c.call(out);
} else {
throw new GroovyRuntimeException("Trying to evaluate a GString containing a Closure taking "
+ c.getMaximumNumberOfParameters() + " parameters");
}
} else {
InvokerHelper.write(out, value);
}
}
}
return out;
}
Notice that writeTo method examines what the values passed for interpolation are, and in case of closure, it invokes it. This is the way GString handles lazy-evaluation of interpolated values.
Now let's take a look at a few examples. Let's assume we want to print a GString and interpolate a value returned by some method call. This method will also print something to the console, so we can see if the method call was triggered eagerly or lazily.
Ex.1: Eager evaluation
class GStringLazyEvaluation {
static void main(String[] args) {
def var = 1
def str = "${loadValue(var++)}"
println "Starting the loop..."
5.times {
println str
}
println "Loop ended..."
}
static Integer loadValue(int val) {
println "This method returns value $val"
return val
}
}
The output:
This method returns value 1
Starting the loop...
1
1
1
1
1
Loop ended...
The default eager behavior. The method loadValue() was invoked before we have printed out str to the console.
Ex.2: Lazy evaluation
class GStringLazyEvaluation {
static void main(String[] args) {
def var = 1
def str = "${ -> loadValue(var++)}"
println "Starting the loop..."
5.times {
println str
}
println "Loop ended..."
}
static Integer loadValue(int val) {
println "This method returns value $val"
return val
}
}
The output:
Starting the loop...
This method returns value 1
1
This method returns value 2
2
This method returns value 3
3
This method returns value 4
4
This method returns value 5
5
Loop ended...
In the second example, we take advantage of lazy evaluation. We define str with a closure that invokes loadValue() method and this invocation is executed when we explicitly print the str to the console (to be more specific - when the GString.toString() method gets executed).
Ex.3: Lazy evaluation and closure memoization
class GStringLazyEvaluation {
static void main(String[] args) {
def var = 1
def closure = { -> loadValue(var++)}
def str = "${closure.memoize()}"
println "Starting the loop..."
5.times {
println str
}
println "Loop ended..."
}
static Integer loadValue(int val) {
println "This method returns value $val"
return val
}
}
The output:
Starting the loop...
This method returns value 1
1
1
1
1
1
Loop ended...
And here is the example you most probably look for. In this example, we still take advantage of lazy evaluation thanks to the closure parameter. However, in this case, we use closure's memoization feature. The evaluation of the string is postponed to the first GString.toString() invocation and closure's result gets memorized, so the next time it gets called it returns the result instead of re-evaluating the closure.
What is the difference between ${{->myVar1}} and ${->myVar1}?
As it was mentioned earlier, GString.toString() method uses GString.writeTo(out) that checks if the given placeholder stores a closure for a lazy evaluation. Every GString instance store placeholder values in the GString.values array and it gets initialized during GString initialization. Let's consider the following example:
def str = "${myVar1} ... ${-> myVar1} ... ${{-> myVar1}}"
Now let's follow GString.values array initialization:
${myVar1} --> evaluates `myVar1` expression and copies its return value to the values array
${-> myVar1} --> it sees this is closure expression so it copies the closure to values array
${{-> myVar1}} --> evaluates `{-> myVar1}` which is closure definition expression in this case and copies its return value (a closure) to the values array
As you can see, in the 1st and 3rd example it did exactly the same - it evaluated the expression and stored it in the GString.values array of type Object[]. And here is the crucial part: the expression like {->something} is not a closure invocation expression. The expression that evaluates the closure is
{->myVar1}()
or
{->myVar1}.call()
It can be illustrated with the following example:
def str = "${println 'B'; 2 * 4} ${{ -> println 'C'; 2 * 5}} ${{ -> println 'A'; 2 * 6}.call()}"
println str
Values initialization is as follows:
${println 'B'; 2 * 4} ---> evaluates the expression which prints 'B' and returns 8 - this value is stored in values array.
${{ -> println 'C'; 2 * 5}} ---> evaluates the expression which is nothing else than creation of a closure. This closure is stored in the values array.
${{ -> println 'A'; 2 * 6}.call()}" ---> evaluates the expression which creates a closure and then calls it explicitely. It prints 'A' and returns 12 which is stored in the values array at the last index.
That is why after GString object initialization we end up with values array like:
[8, script$_main_closure1, 12]
Now, the creation of this GString caused a side effect - the following characters shown on the console:
B
A
This is because the 1st and the 3rd values evaluation invoked println method call.
Now, when we finally call println str which invokes GString.toString() method, all values get processed. When the interpolation process starts it does the following:
value[0] --> 8 --> writes "8"
value[1] --> script$_main_closure1 --> invoke script$_main_closure1.call() --> prints 'C' --> returns 10 --> 10 --> writes "10"
value[2] --> 12 --> writes "12"
That is why the final console output looks like this:
B
A
C
8 10 12
This is why in practice expressions like ${->myVar1} and ${{->myVar1}} are similar. In the first case GString initialization does not evaluate the closure expression and puts it directly to the values array, in the second example placeholder gets evaluated and the expression it evalutes creates and returns the closure which then gets stored in the values array.
Note on Groovy 3.x
If you try to execute the expression ${{->myVar1}} in Groovy 3.x you will end up with the following compiler error:
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
General error during conversion: java.lang.NullPointerException
java.lang.NullPointerException
at org.apache.groovy.parser.antlr4.AstBuilder.lambda$visitGstring$28(AstBuilder.java:3579)
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1382)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
at org.apache.groovy.parser.antlr4.AstBuilder.visitGstring(AstBuilder.java:3591)
at org.apache.groovy.parser.antlr4.AstBuilder.visitGstring(AstBuilder.java:356)
at org.apache.groovy.parser.antlr4.GroovyParser$GstringContext.accept(GroovyParser.java:4182)
at groovyjarjarantlr4.v4.runtime.tree.AbstractParseTreeVisitor.visit(AbstractParseTreeVisitor.java:20)
at org.apache.groovy.parser.antlr4.AstBuilder.visit(AstBuilder.java:4287)
.....
at org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:565)
at org.codehaus.groovy.tools.FileSystemCompiler.compile(FileSystemCompiler.java:72)
at org.codehaus.groovy.tools.FileSystemCompiler.doCompilation(FileSystemCompiler.java:240)
at org.codehaus.groovy.tools.FileSystemCompiler.commandLineCompile(FileSystemCompiler.java:163)
at org.codehaus.groovy.tools.FileSystemCompiler.commandLineCompileWithErrorHandling(FileSystemCompiler.java:203)
at org.codehaus.groovy.tools.FileSystemCompiler.main(FileSystemCompiler.java:187)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.codehaus.groovy.tools.GroovyStarter.rootLoader(GroovyStarter.java:114)
at org.codehaus.groovy.tools.GroovyStarter.main(GroovyStarter.java:136)
1 error

int type > operator error in Groovy

Using Groovy, the > operator warns that the type matches wrong.
This is a problem:
def greaterThan50(nums){
def result = []
nums.each{ num ->
if(num > 50)
result << num
result
}
def test = greaterThan50([2, 3, 50, 62, 11, 2999])
assert test.contains(62)
The line " if(num > 50)" create the warning.
[Static type checking] - Cannot find matching method java.lang.Object#compareTo(java.lang.Integer). Please check if the declared type is right and if the method exists.
50 is int type (and also an object as I know) but the site's example is like this. (site is here : http://docs.smartthings.com/en/latest/getting-started/groovy-basics.html#groovy-basics)
def greaterThan50(nums){
def result = []
for (num in nums){
if(num > 50){
result << num
}
}
result
}
def test = greaterThan50([2, 5, 62, 50, 25, 88])
How do I change code for comparing two int types?
For type-checking to work properly, you need to specify argument and return types explicitly. You also missed a closing brace to end the closure after each.
List<Integer> greaterThan50(List<Integer> nums) {
def result = []
nums.each { num ->
if (num > 50)
result << num
}
result
}
def test = greaterThan50([2, 3, 50, 62, 11, 2999])
assert test.contains(62)
A groovyier way to archive the same functionality would be
nums.findAll { it > 50 }
which creates a new list and adds all numbers that satisfy the condition for you.
If you need to suppress the warning, effectively making that code statically checked, you can declare the data type of the incoming parameter explicitly.
def greaterThan50(List<Integer> nums){
This will allow static type checking to link the iteration element type to Integer.

Default return value of a function in BASIC

Following is an example program in BASIC. Can someone tell me what this function returns if the marked condition is not true? I have to port the program to C++ and need to understand it. I have no BASIC knowledge - please bear with simple question.
FUNCTION CheckPoss (u)
tot = tot + 1
f = 0
SELECT CASE u
CASE 2
f = f + CheckIntersection(1, 3, 2, 1) 'A
CASE 3
f = f + CheckIntersection(2, 3, 3, 1) 'B
END SELECT
IF f = 0 THEN <============== This condition if true,
CheckPoss = 1 <============== then return value is 1
IF u = 9 THEN
PrintSolution
END IF
END IF
END FUNCTION
This is a good example of bad programming. First some unknown global variable is changed in this function. "tot = tot + 1"! Second line "F" is another unknown global variable is assigned "0". Or is this the only place this variable is used? In that case it is a variant implicitly declared here. Use a dim to declare it. It is legal in basic to do this. Globals should be passed as arguments to the function like this:
function CheckPoss(u as integer, tot as integer) as integer
dim f as integer
f=0
It is all about good practice so the input is clear and output is clear and all variable assignments should be through arguments passed to the function.
The return type is not declared either. Is this visual basic? or is it some older basic? Anyway the return type is a variant in case of visual basic. Older basic would be an integer type.
The output from this function will mostly likely be a zero if the condition is not met! That should also be clear in the code and it is not clear as it is, and I understand why you ask. I am amazed this piece of code comes from a working program.
Good luck with your project!
I don't know exactly that this function do.
On VB.net, the function follow the structure:
Public function CheckPoss(Byval u as integer)
... ' Just commands
return u ' Or other variable
end function
If not exist the 'return' command, the return of function will be 'null' character.
On C, The function will be:
int CheckPoss(int u){
tot++; // Increment tot variable (need be declared)
int f = 0;
switch(u){
case 2:
f += CheckIntersection(1, 3, 2, 1); // A
break;
case 3:
f += CheckIntersection(2, 3, 3, 1); // B
break;
}
if (f == 0){
if (u == 9){
PrintSolution();
}
return 1;
}
}
The return command need be the last command of this function. At case f != 0, the function must return trash (some value or character).
My suggestion is:
int CheckPoss(int u){
tot++; // I think that this must count how times you call this function
int f;
if(u == 2){
f = CheckIntersection(1, 3, 2, 1); // A
}else if(u == 3){
f = CheckIntersection(2, 3, 3, 1); // B
}else{
f = 1; // Case else
}
if (f == 0){
if (u == 9)
PrintSolution();
return 1;
}
}

How does type promotion work in Groovy?

Consider the following code snippet -
def factorial(number) {
if(number == 1)
return number;
else
return number * factorial(number - 1);
}
println factorial(50)
println factorial(50).getClass()
println()
println 45**20
println ((45**20).getClass())
The output is -
0
class java.lang.Integer
1159445329576199417209625244140625
class java.math.BigInteger
Questions -
Why doesn't groovy automatically promote the result of number * factorial(number-1) to a BigInt in the first case?
Why is the output 0? Why isn't it some random number that we should get after integer overflow?
Old question but I'll try to answer both parts of the question:
Groovy documentation on arithmetic operations states that
binary operations involving subclasses of java.lang.Number automatically convert their arguments according to the following matrix (except for division, which is discussed below)
I won't paste the matrix but it specifies no casting to BigInteger or BigDecimal unless one of the operators is of one of these types.
In the case of division:
The division operators "/" and "/=" produce a Double result if either operand is either Float or Double and a BigDecimal result otherwise
I think that the table is not considering the power operator (**) since it's not present in Java and as stated in #tim_yates comment, power implementation uses BigInteger by default.
The code in DefaultGroovyMethods.java shows clearly that the power of int's is calculated using BigInteger's and if the result is small then is cast down to int again (And that's why (2**4).class is java.lang.Integer):
public static Number power(Integer self, Integer exponent) {
if (exponent >= 0) {
BigInteger answer = BigInteger.valueOf(self).pow(exponent);
if (answer.compareTo(BI_INT_MIN) >= 0 && answer.compareTo(BI_INT_MAX) <= 0) {
return answer.intValue();
} else {
return answer;
}
} else {
return power(self, (double) exponent);
}
}
To confirm the behaviour of other operations you can go to IntegerMath, LongMath or other classes in the org.codehaus.groovy.runtime.typehandling package
With Groovy, Integer.multiply( Integer ) always returns an Integer.
The factorial method starts overflowing around step 16.
At step 34, you end up with -2147483648 * -2147483648 which returns 0 so the result will always be 0
One fix is to change your method declaration to:
def factorial( BigInteger number ) {

Is There a Groovier Way To Add Dashes to a String?

I have the following code, which works, but I'm wondering if there is a "groovier" way of doing this:
/**
* 10 digit - #-######-##-#
* 13 digit - ###-#-######-##-#
* */
private formatISBN(String isbn) {
if (isbn?.length() == 10) {
def part1 = isbn.substring(0, 1)
def part2 = isbn.substring(1, 7)
def part3 = isbn.substring(7, 9)
def part4 = isbn.substring(9, 10)
return "${part1}-${part2}-${part3}-${part4}"
} else if (isbn?.length() == 13) {
def part1 = isbn.substring(0, 3)
def part2 = isbn.substring(3, 4)
def part3 = isbn.substring(4, 10)
def part4 = isbn.substring(10, 12)
def part5 = isbn.substring(12, 13)
return "${part1}-${part2}-${part3}-${part4}-${part5}"
} else {
return isbn
}
}
You could first use the [] string operator to get the substrings instead of substring and drop the intermediate variables. For example in the case for length == 10:
"${isbn[0]}-${isbn[1..6]}-${isbn[7..8]}-${isbn[9]}"
Now, there is a bit of repetition there. You can get instead first get all the isbn segments and then .join them with '-':
[isbn[0], isbn[1..6], isbn[7..8], isbn[9]].join('-')
And, even further, instead of referencing isbn every time, you can make a list of the ranges you want to get and then get them all the same time using collect:
[0, 1..6, 7..8, 9].collect { isbn[it] }.join('-')
If you're going for code golfing, you can also do:
('-'+isbn)[1, 0, 2..7, 0, 8..9, 0, 10]
I'll leave it to you to figure out how that works, but i guess it's probably not a good idea to leave that on production code, unless you want to surprise future maintainers hehe.
Also, notice that the format when length == 13 is the same as for length == 10 but with a different prefix, you can then reuse the same function in that case. The whole function (with a couple of tests) would be:
/**
* 10 digit - #-######-##-#
* 13 digit - ###-#-######-##-#
**/
def formatIsbn(isbn) {
switch (isbn?.length()) {
case 10: return [0, 1..6, 7..8, 9].collect { isbn[it] }.join('-')
case 13: return isbn.take(3) + '-' + formatIsbn(isbn.drop(3))
default: return isbn
}
}
assert formatIsbn('abcdefghij') == 'a-bcdefg-hi-j'
assert formatIsbn('abcdefghijklm') == 'abc-d-efghij-kl-m'
Now, i think there are some bad smells in that code. Can isbn be null? At least to me, this doesn't look like a function that needs to bother about the nullity of its argument, or at least that's not clear by reading its name (it should be called something like formatIsbnOrNull instead if both ISBN strings and null values are accepted). If null values are not valid, then let it blow up with a NullPointerException when accessing isbn.length() so the caller know they have passed a wrong argument, instead of silently returning the same null.
The same goes for the return ISBN at the end. Is it expected for that function to receive a string that's neither 10 nor 13 characters long? If not, better throw new IllegalArgumentException() and let the caller know they have called it wrongly.
Finally, i'm not sure if this is the most "readable" solution. Another possible solution is having a string for the format, like '###-#-######-##-#' and then replace the #s by the isbn characters. I think it might be more self-documenting:
def formatIsbn(isbn) {
def format = [
10: '#-######-##-#',
13: '###-#-######-##-#'
][isbn.length()]
def n = 0
format.replaceAll(/#/) { isbn[n++] }
}
Consider adding the method to the String class, as shown here. Note that this answer is a spin on a clever suggestion in epidemian's answer (re: collect).
Note:
This code augments String with asIsbn().
The range [0..2] does not need the call to asIsbn(), but the symmetry of using collect twice is irresistable.
Groovy returns the last expression in if/else, so 'return' is not necessary
/**
* 10 digit - #-######-##-#
* 13 digit - ###-#-######-##-#
**/
String.metaClass.asIsbn = { ->
if (delegate.length() == 10) {
[0, 1..6, 7..8, 9].collect { delegate[it] }.join('-')
} else if (delegate.length() == 13) {
[0..2, 3..12].collect { delegate[it].asIsbn() }.join('-')
} else {
delegate
}
}
assert "abcdefghij".asIsbn() == 'a-bcdefg-hi-j'
assert "abcdefghijklm".asIsbn() == 'abc-d-efghij-kl-m'
assert "def".asIsbn() == "def"
String s = null
assert s?.asIsbn() == null
I would try using Regex... I think it's pretty much readable if you know how to use regex, and it's javascript inspired syntax in groovy is pretty cool also.
One more thing: it's pretty clear, looking at the capture groups, what your string looks like for the desired formatting.
private formatISBN(String isbn) {
if (isbn?.length() == 10) {
m = isbn =~ /(\d{1})(\d{6})(\d{2})(\d{1})/
return "${m[0][1]}-${m[0][2]}-${m[0][3]}-${m[0][4]}"
} else if (isbn?.length() == 13) {
m = isbn =~ /(\d{3})(\d{1})(\d{6})(\d{2})(\d{1})/
return "${m[0][1]}-${m[0][2]}-${m[0][3]}-${m[0][4]}-${m[0][5]}"
} else {
return isbn
}
}
Btw, #epidemian suggestion using backreferences is great! I think the code would look like:
private formatISBN(String isbn) {
if (isbn?.length() == 10) {
return isbn.replaceAll(/(\d{1})(\d{6})(\d{2})(\d{1})/, '$1-$2-$3-$4')
} else if (isbn?.length() == 13) {
return isbn.replaceAll(/(\d{3})(\d{1})(\d{6})(\d{2})(\d{1})/, '$1-$2-$3-$4-$5')
} else {
return isbn
}
}
Dunno if I like this any better. I'd make the position map a static final, too.
private isbnify(String isbn) {
def dashesAt = [ 10: [[0,1], [1,7], [7,9], [9,10]],
13: [[0,3], [3,4], [4,10], [10,12], [12,13]]]
def dashes = dashesAt[isbn?.length()]
(dashes == null) ? isbn
: dashes.collect { isbn.substring(*it) }.join('-')
}
Ranges make for a bit less clutter, IMO:
private isbnify3(String isbn) {
def dashesAt = [ 10: [0, 1..6, 7..8, 9],
13: [0..2, 3, 4..9, 10..11, 12]]
def dashes = dashesAt[isbn?.length()]
dashes == null ? isbn : dashes.collect { isbn[it] }.join("-")
}
With an inject-with-two-accumulators it should be easy to do a list-of-dash-positions version, too.
This should be a comment to #everton, but I don't have the 50 reputation needed to do that yet. So this answer is really just a suggested variation on #everton's answer.
One less regex by making the first 3 digits optional. The downside is having to remove a leading '-' if the ISBN is 10 characters. (I also prefer \d over \d{1}.)
private formatISBN(String isbn) {
String result = isbn.replaceAll(/^(\d{3})?(\d)(\d{6})(\d{2})(\d)$/,
'$1-$2-$3-$4-$5')
if (result) {
return result.startsWith('-') ? result[1..-1] : result
} else {
return isbn // return value unchanged, pattern didn't match
}
}
println formatISBN('1234567890')
println formatISBN('9991234567890')
println formatISBN('123456789') // test an ISBN that's too short
println formatISBN('12345678901234') // test an ISBN that's too long

Resources