views:

80

answers:

4

Below is an implementation of an exercise I've been asked to do (see comments). It works, and the reason I'm posting it is that the function checkMiracle looks like it should be contained in a much smaller loop of code - I'm writing out the same thing plus one ten times. The problem is, I can't seem to find a shorter way of doing it. My question then is can someone point me in any direction of reducing the code in this listing, maybe something to think about that makes it more compact or a 'clever' way of coding it. Any help appreciated. (The exercise sheet is on the JCF so he is forcing us to code this using collections)

/*A 10-digit decimal number N is said to be miraculous if it contains each of the ten decimal digits, and if
the 2-digit number consisting of the first two (most significant, i.e. leftmost) digits of N is divisible by
2, the 3-digit number consisting of the first three digits of N is divisible by 3, and so on up to and including
that N itself is divisible by 10. Write a program to discover a miraculous number (there really is one).
Proceed by making a list of the ten decimal digits, and repeatedly shuffling them until you chance upon an
arrangement that constitutes a miraculous number.
(Note: Type long rather than int is needed for 10-digit decimal integers.) */

import java.util.*;

public class Miracle {

 static private long miracleNum = 0;

 static private ArrayList<Integer> listing = new ArrayList<Integer>();

 static String castValue = "";


 public static void main(String[] args) {

  for(int i = 0; i < 10; i++) listing.add(i); 

  Collections.shuffle(listing);

  while(listing.get(0)==0) Collections.shuffle(listing); //make sure the number doesnt start with zero

  while(!(checkMiracle(listing))) Collections.shuffle(listing);//keep changing it until we get a miracle number



  for(long l : listing) castValue += l;

  miracleNum = Long.parseLong(castValue);

  System.out.println("Miracle num: " + miracleNum);


 }


 static public boolean checkMiracle(ArrayList<Integer> l) {


  long checkValue = Long.parseLong("" + l.get(0) + l.get(1));

  if(checkValue %2 != 0) return false;


  checkValue = Long.parseLong("" + l.get(0) + l.get(1) + l.get(2));

  if(checkValue %3 != 0) return false;


  checkValue = Long.parseLong("" + l.get(0) + l.get(1) + l.get(2) + l.get(3));

  if(checkValue %4 !=0) return false;


  checkValue = Long.parseLong("" + l.get(0) + l.get(1) + l.get(2) + l.get(3) + l.get(4));

  if(checkValue %5 !=0) return false;


  checkValue = Long.parseLong("" + l.get(0) + l.get(1) + l.get(2) + l.get(3) + l.get(4) + l.get(5));

  if(checkValue %6 !=0) return false;


  checkValue = Long.parseLong("" + l.get(0) + l.get(1) + l.get(2) + l.get(3) + l.get(4) + l.get(5) + l.get(6));

  if(checkValue %7 !=0) return false;


  checkValue = Long.parseLong("" + l.get(0) + l.get(1) + l.get(2) + l.get(3) + l.get(4) + l.get(5) + l.get(6) + l.get(7));

  if(checkValue %8 !=0) return false;


  checkValue = Long.parseLong("" + l.get(0) + l.get(1) + l.get(2) + l.get(3) + l.get(4) + l.get(5) + l.get(6) + l.get(7)+ l.get(8));

  if(checkValue %9 !=0) return false;


  checkValue = Long.parseLong("" + l.get(0) + l.get(1) + l.get(2) + l.get(3) + l.get(4) + l.get(5) + l.get(6) + l.get(7)+ l.get(8) + l.get(9));

  if(checkValue %10 !=0) return false;


  return true;


 }


}
+3  A: 

Maybe remove some code duplication using loops:

private static boolean checkMiracleN(List<Integer> l, int n){
   long sum = 0;
   for (int i=0; i<n; i++)
       sum = sum * 10 + l.get(i);
   return sum % n == 0;
}

private static boolean checkMiracle(ArrayList<Integer> l){
    for (int n=2; n<=10; n++)
       if (!checkMiracleN(l, n) 
          return false;
    return true;
}
Thilo
Works bang on, thank you.
Richie
Algorithmically the best would be IMHO to calculate 2 * 3 * 5 * 7 (210), find the first 10 digit number which can be divided by 210 and searching sequantially for the magic number(s), by checking the first, adding 210 and checking the results and so on...
Lajos Arpad
+4  A: 

why don't you gather all the conditions inside a loop? Then you can just add next digit to the number to calculate next one.

String partial = Long.parseLong(l.get(0));
for (int i = 1; i < 10; ++i) {
  partial += Long.parseLong(l.get(i));
  if (Long.valueOf(partial) % (i+1) != 0)
    return false;
}
return true;

in addition you could avoid using strings by using exponentiation digit^(displacement). Having to solve a similar problem by using collections seems groesque..

Jack
+1 for being the only answer that re-uses the partial results to avoid nested loops.
Thilo
partial can be made into a long without using exponentiation as `partial = partial * 10 + l.get(i)` for every step.
Thilo
This doesn't compile (String partial = Long...) and when I change it to what does compile the logic is off and gives wrong answers. Easy to check happily, any answer that isn't 38165472920 is wrong.
Richie
I can't solve your problems. I tried to implement an idea that you should fix by yourself by understanding the conceptual solution, not the written on. We're not here to do your work, but just to help :) So don't complain about that..
Jack
In any case the approach I suggested is absolutely correct. But I don't want to waste time to accomodate the usage of strings and collections instead that plain numbers. That's your duty.
Jack
Fair enough, thanks.
Richie
Don't take it in the wrong way plase.. don't want to be evil.. just think about how my solution works. if your number is 1234567890 first iteration should produce "12", and you check if % 2 = 0, then you add 3 and you check "123" % 3 = 0. In my code I check if % i, but it should have been % (i+1). Just that. I'll edit the post to reflect this issue but think, sincerely, if you just have tried understanding my code you wouldn't have answered that way.
Jack
Ah no, I meant the thank you, there was no sarcasm intended!
Richie
+2  A: 

Use a helper function so that you can replace repeated code with a loop:

static public String GetNumberString(ArrayList<Integer> l, int numDigits)
{
    StringBuilder sb = new StringBuilder();

    for(int i = 0; i < numDigits; i++)
    {
        sb.Append(l.get(i));
    }
    return sb.ToString();
}


static public boolean checkMiracle(ArrayList<Integer> l) {

  long checkValue = 0;

  for (int i = 2; i < 10; i++)
  {
      checkValue = Long.parseLong(GetNumberString(l, i));
      if(checkValue % i != 0) return false;
  }
}

This still means that you're building a very similar string each time. An improvement would be to incrementally build the number on each loop iteration, instead of rebuilding it each time.

RyanHennig
This is really cool, doesn't work unfortunately, I think it's off by one, the answer it gives is not divisibble by ten, it gave me 3816547209 at one point. Will see if I can can git it to work.
Richie
The idea was to show you how to refactor your code to eliminate duplication, not to provide code that is guaranteed to be perfectly bug free :-)
RyanHennig
+1  A: 

A couple of short cuts you can make. e.g. if a number is even its last digit must be even

if (l.get(1) % 2 == 0) return false;

if a number is a multiple of ten, its last digit must be a '0'

if (l.get(9) == 0) return false;

If a number is a multiple if 3, the sum if its digits are multiple of three (same for 9)

If a number is a multiple of 5, its last digit must be a 5 or 0.

In most cases you don't need to * or a %. You definitley don't need to create a String and parse it.

Peter Lawrey