Изменить порядок для циклов?

У меня есть ситуация, когда мне нужно зациклить координаты XYZ в разных порядках в зависимости от ввода пользователя. Так что я область в трехмерном пространстве, а затем набор для петель, как это.

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
       }
    }
 }

но в зависимости от ввода пользователя, порядок может быть следующим.

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
       }
    }
 }

или даже отрицательный.

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
      }
   }
}

Есть ли способ сделать это без жесткого кодирования каждого случая?

3 ответа

Решение

Вот n-мерный степпер, который может шагать в любом количестве измерений в любом порядке от любых начальных местоположений до любых пределов. Смотрите тестовый код для примера.

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();
    }
  }
}

Мои тесты эквивалентов ваших трех сценариев выглядят так:

  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());
  }

Вы сказали, что в зависимости от пользовательского ввода порядок петель меняется. Логика для обработки пользовательского ввода должна быть написана.

Вы можете написать код так:

//Code to populate XInit, XEnd, YInit, YEnd, ZInit, ZEnd based on user input

    for(int x = XInit; x < XEnd; x=XInit<XEnd?x+1:x-1){
       for(int y = YInit; y < YEnd; y=YInit<YEnd?y+1:y-1){
         for(int z = ZInit; z < ZEnd; z=ZInit<ZEnd?z+1:z-1){
            //do stuff
           }
        }
     }

Примечание. Возможно, вы даже захотите абстрагировать вычисление параметров XInit, XEnd и т. Д. В отдельном методе.

Ваши "вещи", скорее всего, получают доступ к значениям x, y и z, так что способ, которым вы жестко программируете, является, вероятно, самым простым для понимания. Ваши имена методов могут четко указывать порядок. Для трех приведенных вами примеров это будет выглядеть примерно так:

public void somethingXYZ(Build build, Stuff stuff) {...}
public void somethingZYX(Build build, Stuff stuff) {...}
public void somethingXnYZ(Build build, Stuff stuff) {...}

Когда вы пишете код и хотите выбрать один из этих методов, ваша IDE даже поможет вам, перечислив доступные варианты для этого класса. Я думаю, то, как вы организуете это, уже сработало бы.

Другие вопросы по тегам