Search code examples
javaloopsdynamic3dcoords

Change order of for loops?


I have a situation where i need to loop though xyz coordinates in different orders depending on a users input. So i an area in 3D space then a set of for loops like so.

for(int x = 0; x < build.getWidth(); x++){
   for(int y = 0; y < build.getHeight(); y++){
     for(int z = 0; z < build.getLength(); z++){
        //do stuff
       }
    }
 }

but depending on the users input, the order may be like this.

for(int z = 0; z < build.getLenght(); z++){
   for(int y = 0; y < build.getHeight(); y++){
     for(int x = 0; x < build.getWidth(); x++){
        //do stuff
       }
    }
 }

or even negative.

for(int x = build.getWidth(); x > 0; x--){
   for(int y = 0; y < build.getHeight(); y++){
      for(int z = 0; z < build.getLength(); z++){
        //do stuff
      }
   }
}

Is there any way to do this without hard coding every case?


Solution

  • Here's an n-dimensional stepper that can step in any number of dimensions in any order from any start locations to any limits. See the test code for an example.

    public class Test {
      public void test() {
        int[] limits = {3, -5, 7};
        int[] order = {0, 2, 1};
        int[] starts = {0, 0, 0};
        int[] steps = {1, -1, 2};
        NDimensionalStepper nds = new NDimensionalStepper(limits, order, starts, steps);
        do {
          System.out.println(nds);
        } while (nds.step());
      }
    
      public static void main(String args[]) {
        new Test().test();
      }
    
      public static class NDimensionalStepper {
        // The current positions in each dimension.
        // Note that i[order[0]] is the fastest mover.
        final int[] i;
        // Starts.
        final int[] starts;
        // Steps.
        final int[] steps;
        // Limits.
        final int[] limits;
        // Order.
        final int[] order;
        // The (unordered) dimension we last stepped.
        int d = 0;
    
        // Full constructor.
        public NDimensionalStepper(int[] limits, int[] order, int[] starts, int[] steps) {
          // Should parameter check to ensure all are the same length.
          // Should also check that each dimension will terminate.
          this.i = Arrays.copyOf(starts, starts.length);
          this.starts = Arrays.copyOf(starts, starts.length);
          this.steps = Arrays.copyOf(steps, steps.length);
          this.limits = Arrays.copyOf(limits, limits.length);
          this.order = Arrays.copyOf(order, order.length);
        }
    
        // Default steps to 1.
        public NDimensionalStepper(int[] limits, int[] order, int[] starts) {
          this(limits, order, starts, defaultSteps(limits, starts));
        }
    
        // Default steps - 1 Towards limits.
        private static int[] defaultSteps(int[] limits, int[] starts) {
          int[] steps = new int[limits.length];
          for (int i = 0; i < limits.length; i++) {
            // Step towrds limits.
            steps[i] = (int) Math.signum(limits[i] - starts[i]);
          }
          return steps;
        }
    
        // Default starts to 0.
        public NDimensionalStepper(int[] limits, int[] order) {
          this(limits, order, defaultStarts(limits.length));
        }
    
        // Default starts - 0, 0, ...
        private static int[] defaultStarts(int d) {
          int[] starts = new int[d];
          Arrays.fill(starts, 0);
          return starts;
        }
    
        // Default order to normal.
        public NDimensionalStepper(int[] limits) {
          this(limits, defaultOrder(limits.length));
        }
    
        // Default order - ..., 1, 0
        private static int[] defaultOrder(int d) {
          int[] order = new int[d];
          for (int i = 0; i < d; i++) {
            order[i] = d - i - 1;
          }
          return order;
        }
    
        // Get the current position in dimension d.
        public int get(int d) {
          return i[d];
        }
    
        // Take just one step. Return false if cant.
        public boolean step() {
          boolean stepped = false;
          boolean finished = false;
          while (!stepped && !finished) {
            // Which dimension should be stepped (depends on order).
            int o = order[d];
            // Can we step in the current dimension?
            while (finished(o) && d < order.length - 1) {
              // Reached a limit! - Move up one dimension.
              o = order[++d];
            }
            if (d < order.length && !finished(o)) {
              // Step it.
              i[o] += steps[o];
              stepped = true;
              // Zero all lower dimensions.
              while (d > 0) {
                d -= 1;
                i[order[d]] = starts[order[d]];
              }
            } else {
              // Got to the last without finding one below limit. Finished!
              finished = true;
            }
          }
          return !finished;
        }
    
        // Equal or passed the limits.
        private boolean finished(int o) {
          int sign = (int) Math.signum(steps[o]);
          return sign * (i[o] + steps[o]) >= sign * limits[o];
        }
    
        @Override
        public String toString() {
          StringBuilder s = new StringBuilder();
          s.append("{");
          for (int d = 0; d < order.length; d++) {
            s.append(get(d));
            if (d < order.length - 1) {
              s.append(",");
            }
          }
          s.append("}");
          return s.toString();
        }
      }
    }
    

    My tests of the equivalents of your three scenarios look like:

      private void testBuild1(Build build) {
        System.out.println("Build: x,y,z");
        for (int x = 0; x < build.getWidth(); x++) {
          for (int y = 0; y < build.getHeight(); y++) {
            for (int z = 0; z < build.getLength(); z++) {
              System.out.println("{" + x + "," + y + "," + z + "}");
            }
          }
        }
        int[] limits = {build.getWidth(), build.getHeight(), build.getLength()};
        testNDS(new NDimensionalStepper(limits));
      }
    
      private void testBuild2(Build build) {
         System.out.println("Build: z,y,x");
        for (int z = 0; z < build.getLength(); z++) {
          for (int y = 0; y < build.getHeight(); y++) {
            for (int x = 0; x < build.getWidth(); x++) {
              System.out.println("{" + x + "," + y + "," + z + "}");
            }
          }
        }
        int[] limits = {build.getWidth(), build.getHeight(), build.getLength()};
        int[] order = {0,1,2};
        testNDS(new NDimensionalStepper(limits, order));
      }
    
      private void testBuild3(Build build) {
        System.out.println("Build: x--,y,z");
        for (int x = build.getWidth(); x > 0; x--) {
          for (int y = 0; y < build.getHeight(); y++) {
            for (int z = 0; z < build.getLength(); z++) {
              System.out.println("{" + x + "," + y + "," + z + "}");
            }
          }
        }
        int[] limits = {0, build.getHeight(), build.getLength()};
        int[] order = {2,1,0};
        int[] starts = {build.getWidth(), 0, 0};
        int[] steps = {-1, 1, 1};
        testNDS(new NDimensionalStepper(limits, order, starts, steps));
      }
    
      private void testNDS(NDimensionalStepper nds) {
        System.out.println("--nds--");
        do {
          System.out.println(nds);
        } while (nds.step());
      }