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

У меня есть ситуация, когда мне нужно зациклить координаты xyz в разных порядках в зависимости от ввода пользователей. Итак, я область в трехмерном пространстве, а затем набор циклов for, например так.

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

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


person Antonio    schedule 30.03.2013    source источник
comment
Вместо этого используйте итераторы. Ваша программа жестко запрограммирована на итерацию по ним как iter1, iter2, iter3, но перед вводом циклов настройте их в соответствии с пользовательским вводом.   -  person aioobe    schedule 31.03.2013
comment
Это может сработать. Мне просто нужно настроить некоторые коллекции для координат xyz и изменить их, если мне нужно в противоположном направлении.   -  person Antonio    schedule 31.03.2013
comment
@aioobe, если бы это был ответ, я бы проголосовал за него!   -  person Wayne Uroda    schedule 31.03.2013


Ответы (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());
  }
person OldCurmudgeon    schedule 31.03.2013

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

Вы можете кодировать следующим образом:

//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 и т. д. в отдельный метод.

person prashant    schedule 30.03.2013
comment
Не могли бы вы дать полный ответ? Какие значения должен иметь XInit, XEnd, YInit, YEnd, ZInit, ZEnd для case1, case2 и case3? - person Walery Strauch; 31.03.2013
comment
Я не указал это, потому что это зависит от логики, используемой приложением. Связь между пользовательским вводом и порядком итерации не ясна из вопроса. - person prashant; 31.03.2013

Ваш «материал», скорее всего, обращается к значениям 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 даже поможет вам, перечислив доступные параметры для этого класса. Я думаю, то, как вы это организуете, уже сработало бы хорошо.

person phatfingers    schedule 30.03.2013