7
$\begingroup$

So, take a Java statement like this one, given some int n:

if ((n+"").length() == (((n+1)+"").length())) {

Or perhaps this one, given some double, temperature, and some method that returns a boolean, unHatched():

while (temperature < 100 && unHatched())

In both of these cases, multiple auto-casts are made during the evaluation of the statement, and in both cases, the final determination is a boolean that is passed into some sort of control structure.

In my work with gifted students, the most that I have ever had to do to help someone understand statements like these is to do a simple trace. We write the statement down, mark each item by its type, and then cross off areas of the statement (and write down the new types) as we walk through the evaluation. This method has worked pretty consistently with my students at my day-job.

However, lately I have begun tutoring an adult student who is having a great deal of trouble, and I have been struggling to break apart statements like the above examples in ways that he can parse and keep straight.

He is at the right point, curricularly-speaking, to be examining them, but I am really struggling to help him trace and understand such statements. (Ironically enough, because he can guess and modify a statement repeatedly, he can actually write statements like these with a fair amount of success, though when he is finished, he no longer fully understands them.)

For various reasons, learning a small concept usually takes him 3-5 weeks. This timescale is fine, it is simply the speed at which he operates. However, this adds a constraint for me: approaches that I take to the material must lend themselves to a fair amount of repetition with only very minor variations, or they won't be effective.

I am really having trouble finding a good approach here. How can I give him the tools to break down statements with complex data type transformations?

$\endgroup$
3
  • 2
    $\begingroup$ That 2nd example while (temperature < 100 && unHatched()), has no cast or type conversion: < takes 2 comparable, and returns a boolean, then && takes 2 booleans and returns a boolean, then while takes a boolean. $\endgroup$ Commented Aug 9, 2017 at 21:17
  • $\begingroup$ May be he could write such statements, but he should know (and use) other ways to express the same idea. For example : write auxiliary functions (here numberOfDigits(int n)), use superfluous parentheses to help readability ( around temperature<100). The key challenge in programming is to manage the complexity and the readability of the text we produce. The milk quickly goes over. $\endgroup$ Commented Aug 15, 2017 at 15:20
  • $\begingroup$ Another point : be careful with such exercices. Some students could conclude they are expected to write programs this way if they want to look smart. $\endgroup$ Commented Aug 15, 2017 at 17:45

5 Answers 5

4
$\begingroup$

I suspect that you handle such situations by informally building the parse tree of the expression in your mind. Maybe not all at once, but a bit at a time, perhaps. I'm not a cognition expert, of course, but it might help him if you teach him to do it explicitly on paper. He needs a table of operator precedence, of course and needs to be able to deal with parentheses, but otherwise it is just a mechanical process. Put the lowest precedence operator at the root of a tree and then work on the operands. The interior nodes are operators and the leaves are operands.

Can't promise it will work, but it would be my next step. With time, he may learn to rely less on the paper. Or, he might learn how to build a tool to do it automatically. That might be nice to have.

$\endgroup$
4
$\begingroup$

I'd maybe suggest an exercise of splitting these kinds of statements up into multiple variables and lines of code.

So take this line:

if ((n+"").length() == (((n+1)+"").length())) {

And come up with this code:

String nString = n + "";
int nLength = stringN.length();
int nPlusOne = n+1;
String nPlusOneString = nPlusOne + "";
int nPlusOneLength = nPlusOneString.length();
boolean nLengthEqualsNPlusOneLength = (nLength == nPlusOneLength);
if(nLengthEqualsNPlusOneLength){

This is what you're asking the student to do in their head, so maybe the intermediate step of actually writing out the breakdown like this would help. Make sure the student uses descriptive variable names so they can more easily keep track of what each step is doing.

If you do this process of breaking a line down into smaller sub-lines enough, eventually you'll become more "natural" at it, and you won't have to explicitly break it down anymore.

It's worth noting that if I saw a line like the original in "the real world" I would ask the author of the code to split it up into multiple pieces, or at least add a comment or isolate it into a self-descriptive function.

$\endgroup$
9
  • 1
    $\begingroup$ You have broken this into steps that are too small. There is a lot of namespace clutter here. $\endgroup$ Commented Aug 13, 2017 at 20:12
  • $\begingroup$ @ncmathsadist What exactly do you mean by "namespace clutter" in this context? I would assume these are all local variables inside a function. Also please keep in mind that the point of this exercise is to get the student to a point where they no longer need to do this. Code you write while learning != code you write in real life. $\endgroup$ Commented Aug 13, 2017 at 20:43
  • $\begingroup$ too many variable names. $\endgroup$ Commented Aug 14, 2017 at 1:14
  • $\begingroup$ @ncmathsadist You understand that the point of this exercise is to break it down into smaller steps, right? And that this type of code is meant for learning and not for production? $\endgroup$ Commented Aug 14, 2017 at 2:17
  • $\begingroup$ but not too small. $\endgroup$ Commented Aug 14, 2017 at 13:33
3
$\begingroup$

I think the student's problem is not with types, but first with the parsing of complex expressions (not to be confused with the tracing of it's evaluation). Mentally building the abstract syntax tree from the linear text representation.

First rewrite remove the extra parentheses at right, and introduce a line break

if (    (n+"").length() 
     == ((n+1)+"").length()) {

to split the expression. It make clear you are comparing two lengths = ints.

Second make it obvious that the 2 sides of the comparison have the same pattern

if (    (n    +"").length() 
     == ((n+1)+"").length()) {

a cosmetic change which drastically reduces the problem space : there's a hope that if we can figure what happens at the left for n, we'll understand the right part too.

What's left is producing a string from an int. Not the best way (what about Integer.toString ?) but anyway.

Actually, only one typecast is involved, twice.

abstract tree

As for "writing code he can't understand later", it'a common symptom. The problem is not he cannot understand it, but that he wrote too complex code in the first place. Beginners always overestimate their cognition abilities. And they are reluctant to decomposition because it requires an effort to clearly identify a sub-task (and find a nice name for it !)

Remedies : insist on

  • using auxiliary variables
  • define and use of "helper" functions, predicates.
$\endgroup$
1
$\begingroup$

You say “when he is finished, he no longer fully understands them.”

There is a good practice to write self documenting code. This is done not with comments, but with well named entities (methods, classes, objects). This helps you to see what the code does (I do not mean how it does it, but in turn it will help you understand how it works).

Therefore teach him how to do self documenting code.

Example if ((n+"").length() == (((n+1)+"").length())) {

Do

private static boolean digitsInInteger( int n ) {
    return n.ToString().length();
}

private static boolean integersHaveSameNumberOfDigits(int a, int b){
    return digitsInInteger(a) == digitsInInteger(b);
}

if ( integersHaveSameNumberOfDigits( n, n+1 ) ) { …

You can even put integersHaveSameNumberOfDigits( n, n+1 ) into a function, but I did not know what to call it.

Example while (temperature < 100 && unHatched())

Do

private static boolean isColdEnoughNotToCookEggs( int temperature ){
    return temperature < 100;
}

private boolean eggsAreStillViable(){
    return isColdEnoughNotToCookEggs(temperature) && eggs.areUnHatched();
}

while (eggsAreStillViable() ){ …

This example was a bit harder to refactor, as I had to guess the intent.


Code in this answer may not be Java.

$\endgroup$
7
  • $\begingroup$ How To call ? isNotAPowerOf10minus1 (int n) $\endgroup$ Commented Aug 11, 2017 at 14:47
  • $\begingroup$ @MichelBillaud can you repeat your comment, with context and grammar; I have no idea what its meaning is. $\endgroup$ Commented Aug 15, 2017 at 9:37
  • $\begingroup$ n and n+1 (I suppose n is strictly positive) have the same number of digits (in the decimal representation), except when n is made of sequence of 9's. That is when n = 10^k - 1, for some k (and then n+1 = 10^k. n has k-1 digits, n+1 has k. Thus the name of a predicate should state something about n not being a power of 10 minus 1. $\endgroup$ Commented Aug 15, 2017 at 10:45
  • $\begingroup$ @MichelBillaud I grok you now. However to name something well, you have to know the intention. It should not be based on how it does it, but on what it does. That is what externally visible behaviour it has. $\endgroup$ Commented Aug 15, 2017 at 11:02
  • $\begingroup$ Of course, you're right. boolean hasTheSameNumberOfDigitsAsItsSuccessor(int n) { return ! isAPowerOf10(n+1); } $\endgroup$ Commented Aug 15, 2017 at 15:01
-1
$\begingroup$

Package this in a static method like so. You are only really interested in whether the successor of a number has more digits than the number. This is clean and it avoids interface clutter. It also keeps the intermediate variables local; unless you have a need, you do not want to expose them in your interface. Note it is static since it is state-invariant.

/**
* This <code>static</code> method tells if an integer's successor has
* more digits than the integer. * @param n the integer you are checking * @return true if the integer <code>n</code>'s successor has more * digits than <code>n</code>. */
public static boolean successorLonger(n) { String nString = "" + n; String succString = "" + (n + 1); return nString.length() != succString.length(); }

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.