Есть ли способ сделать вложенные циклы n-уровня в Java?

StackOverflow https://stackoverflow.com/questions/426878

Вопрос

Другими словами, могу ли я сделать что-то вроде

for() {
    for {
       for {
       }
    }
}

Кроме N раз?Другими словами, когда вызывается метод, создающий циклы, ему дается некоторый параметр N, и затем метод создаст N этих циклов, вложенных один в другой?

Конечно, идея состоит в том, что должен быть «легкий» или «обычный» способ сделать это.У меня уже есть идея для очень сложной задачи.

Это было полезно?

Решение

Похоже, вы можете захотеть изучить рекурсию.

Другие советы

jjnguy прав; рекурсия позволяет динамически создавать вложенность переменной глубины. Тем не менее, вы не получите доступ к данным из внешних слоев без дополнительной работы. & Quot; in-line-nested & Quot; случай:

for (int i = lo; i < hi; ++i) {
    for (int j = lo; j < hi; ++j) {
        for (int k = lo; k < hi; ++k) {
            // do something **using i, j, and k**
        }
    }
}

хранит переменные i, j и k в области видимости для самого внутреннего тела.

Вот один быстрый способ сделать это:

public class NestedFor {

    public static interface IAction {
        public void act(int[] indices);
    }

    private final int lo;
    private final int hi;
    private final IAction action;

    public NestedFor(int lo, int hi, IAction action) {
        this.lo = lo;
        this.hi = hi;
        this.action = action;
    }

    public void nFor (int depth) {
        n_for (0, new int[0], depth);
    }

    private void n_for (int level, int[] indices, int maxLevel) {
        if (level == maxLevel) {
            action.act(indices);
        } else {
            int newLevel = level + 1;
            int[] newIndices = new int[newLevel];
            System.arraycopy(indices, 0, newIndices, 0, level);
            newIndices[level] = lo;
            while (newIndices[level] < hi) {
                n_for(newLevel, newIndices, maxLevel);
                ++newIndices[level];
            }
        }
    }
}

Интерфейс IAction определяет роль управляемого действия, которое принимает массив индексов в качестве аргумента для своего метода act.

В этом примере каждый экземпляр NestedFor настраивается конструктором с ограничениями итерации и действием, выполняемым на самом внутреннем уровне. Параметр метода nFor указывает, как глубоко вкладывать.

Вот пример использования:

public static void main(String[] args) {
    for (int i = 0; i < 4; ++i) {
        final int depth = i;
        System.out.println("Depth " + depth);
        IAction testAction = new IAction() {
            public void act(int[] indices) {
                System.out.print("Hello from level " + depth + ":");
                for (int i : indices) { System.out.print(" " + i); }
                System.out.println();
            }
        };
        NestedFor nf = new NestedFor(0, 3, testAction);
        nf.nFor(depth);
    }
}

и (частичный) вывод его выполнения:

Depth 0
Hello from level 0:
Depth 1
Hello from level 1: 0
Hello from level 1: 1
Hello from level 1: 2
Depth 2
Hello from level 2: 0 0
Hello from level 2: 0 1
Hello from level 2: 0 2
Hello from level 2: 1 0
Hello from level 2: 1 1
Hello from level 2: 1 2
Hello from level 2: 2 0
Hello from level 2: 2 1
Hello from level 2: 2 2
Depth 3
Hello from level 3: 0 0 0
Hello from level 3: 0 0 1
Hello from level 3: 0 0 2
Hello from level 3: 0 1 0
...
Hello from level 3: 2 1 2
Hello from level 3: 2 2 0
Hello from level 3: 2 2 1
Hello from level 3: 2 2 2

Возможно, вы захотите объяснить, что вы действительно хотите сделать.

Если внешние циклы for ничего не делают, кроме управления счетчиком, то ваши вложенные циклы <=> - это просто более сложный способ итерации с помощью счетчика, который может обрабатываться одним циклом <=>.

Например:

for (x = 0; x < 10; ++x) {
  for (y = 0; y < 5; ++y) {
    for (z = 0; z < 20; ++z) {
      DoSomething();
    }
  }
}

эквивалентно

for (x = 0; x < 10*5*20; ++x) {
  DoSomething();
}

Я действительно думал об этом на днях.

Примером, который, вероятно, не идеален, но довольно близок к тому, о чем я думаю, было бы распечатать дерево каталогов

public void printTree(directory) {
   for(files in directory) {
      print(file);
      if(file is directory) {
          printTree(file);
      }
   }
}

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

Правка 2015 года. В то же время, что и в предыдущем заклинании, я сделал следующий пакет, чтобы справиться с этим; https://github.com/BeUndead/NFor

Использование будет следующим:

public static void main(String... args) {
    NFor<Integer> nfor = NFor.of(Integer.class)
            .from(0, 0, 0)
            .by(1, 1, 1)
            .to(2, 2, 3);

    for (Integer[] indices : nfor) {
        System.out.println(java.util.Arrays.toString(indices));
    }
}

в результате

[0, 0, 0]
[0, 0, 1]
[0, 0, 2]
[0, 1, 0]
[0, 1, 1]
[0, 1, 2]
[1, 0, 0]
[1, 0, 1]
[1, 0, 2]
[1, 1, 0]
[1, 1, 1]
[1, 1, 2]

Он также поддерживает условия, отличные от lessThan. Использование там было (с import static NFor.*;):

NFor<Integer> nfor = NFor.of(Integer.class)
        .from(-1, 3, 2)
        .by(1, -2, -1)
        .to(lessThanOrEqualTo(1), greaterThanOrEqualTo(-1), notEqualTo(0));

В результате:

[-1, 3, 2]
[-1, 3, 1]
[-1, 1, 2]
[-1, 1, 1]
[-1, -1, 2]
[-1, -1, 1]
[0, 3, 2]
[0, 3, 1]
[0, 1, 2]
[0, 1, 1]
[0, -1, 2]
[0, -1, 1]
[1, 3, 2]
[1, 3, 1]
[1, 1, 2]
[1, 1, 1]
[1, -1, 2]
[1, -1, 1]

Очевидно, что поддерживаются циклы разной длины и разных классов (все в штучной упаковке, числовые примитивы). По умолчанию (если не указано) используется значение (0, ...). By (1, ...); но a (...) должен быть указан.

Файл NForTest должен демонстрировать несколько различных способов его использования.

Основной предпосылкой этого является простое продвижение «индексов» каждый ход, а не использование рекурсии.

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

Основная идея вложенных циклов - умножение .

Если продолжить ответ Михаэля Барра, если внешние циклы for ничего не делают, кроме управления счетом, то ваши вложенные циклы n над счетами X - это просто более сложный способ перебора произведения счетчиков. с одним Y циклом.

Теперь давайте расширим эту идею до списков. Если вы перебираете три списка во вложенных циклах, это просто более сложный способ перебора продуктов списков с помощью одного цикла. Но как выразить произведение трех списков?

Во-первых, нам нужен способ выражения произведения типов. Продукт двух типов P2<X, Y> и P3<A, B, C> можно выразить в виде универсального типа, например List<X>. Это просто значение, которое состоит из двух значений: одно типа List<Y>, другое типа List<Z>. Это выглядит так:

public abstract class P2<A, B> {
  public abstract A _p1();
  public abstract B _p2();
}

Для продукта трех типов у нас просто есть List<P3<X, Y, Z>> с очевидным третьим методом. Таким образом, произведение трех списков достигается путем распределения функтора List по типу продукта. Таким образом, произведение List, doSomething и <=> просто <=>. Затем вы можете перебрать этот список с помощью одного цикла.

библиотека функциональных Java имеет тип <=>, который поддерживает умножение списков вместе с использованием первоклассных функций и типов продуктов. (P2, P3 и т. Д., Которые также включены в библиотеку).

Например:

for (String x : xs) {
   for (String y : ys) {
     for (String z : zs) {
       doSomething(x, y, z);
     }
   }
}

эквивалентно

for (P3<String, String, String> p : xs.map(P.p3()).apply(ys).apply(zs)) {
   doSomething(p._1(), p._2(), p._3());
}

Продвигаясь дальше с функциональной Java, вы можете сделать <=> первоклассным, как показано ниже. Допустим, <=> возвращает строку:

public static final F<P3<String, String, String>, String> doSomething =
  new F<P3<String, String, String>, String>() {
    public String f(final P3<String, String, String> p) {
      return doSomething(p._1(), p._2(), p._3());
    }
  };

Затем вы можете полностью исключить цикл for и собрать результаты всех применений <=>:

List<String> s = xs.map(P.p3()).apply(ys).apply(zs).map(doSomething);

Если у вас общая структура вложенного цикла, например:

for(i0=0;i0<10;i0++)
    for(i1=0;i1<10;i1++)
        for(i2=0;i2<10;i2++)
            ....
                for(id=0;id<10;id++)
                    printf("%d%d%d...%d\n",i0,i1,i2,...id);

где i0,i1,i2,...,id - переменные цикла, а d - глубина вложенного цикла.

Эквивалентное рекурсивное решение:

void nestedToRecursion(counters,level){
    if(level == d)
        computeOperation(counters,level);
    else
    {
        for (counters[level]=0;counters[level]<10;counters[level]++)
            nestedToRecursion(counters,level+1);
    }
}
void computeOperation(counters,level){
    for (i=0;i<level;i++)
        printf("%d",counters[i]);
    printf("\n");
}

counters - это массив размером i0,i1,i2,...id, представляющий соответствующие переменные int counters[d] соответственно initial[d], ending[d].

nestedToRecursion(counters,0);

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

Самый лучший общий подход, который я смог придумать в Java 7, это

// i[0] = 0..1  i[1]=0..3, i[2]=0..4
MultiForLoop.loop( new int[]{2,4,5}, new MultiForLoop.Callback() { 
    void act(int[] i) { 
        System.err.printf("%d %d %d\n", i[0], i[1], i[2] );
    }
}

Или в Java 8:

// i[0] = 0..1  i[1]=0..3, i[2]=0..4
MultiForLoop.loop( new int[]{2,4,5}, 
   i -> { System.err.printf("%d %d %d\n", i[0], i[1], i[2]; } 
);

Реализация, которая поддерживает это:

/**
 * Uses recursion to perform for-like loop.
 *  
 * Usage is 
 *  
 *    MultiForLoop.loop( new int[]{2,4,5}, new MultiForLoop.Callback() { 
 *        void act(int[] indices) { 
 *            System.err.printf("%d %d %d\n", indices[0], indices[1], indices[2] );
 *        }
 *    }
 *  
 * It only does 0 - (n-1) in each direction, no step or start 
 * options, though they could be added relatively trivially.
 */
public class MultiForLoop {

    public static interface Callback {
        void act(int[] indices);
    }

    static void loop(int[] ns, Callback cb) {
        int[] cur = new int[ns.length];
        loop(ns, cb, 0, cur);
    }

    private static void loop(int[] ns, Callback cb, int depth, int[] cur) {
        if(depth==ns.length) {
            cb.act(cur);
            return;
        }

        for(int j = 0; j<ns[depth] ; ++j ) {
            cur[depth]=j;
            loop(ns,cb, depth+1, cur);
        }
    }
}
String fors(int n){
StringBuilder bldr = new StringBuilder();
for(int i = 0; i < n; i++){
    for(int j = 0; j < i; j++){
        bldr.append('\t');
    }
    bldr.append("for() {\n");
}
for(int i = n-1; i >= 0; i--){
    for(int j = 0; j < i; j++){
        bldr.append('\t');
    }
    bldr.append("}\n");
}
return bldr.toString();
}

Создает хороший вложенный скелет для цикла ;-) Не совсем серьезно, и я знаю, что рекурсивное решение было бы более элегантным.

public void recursiveFor(Deque<Integer> indices, int[] ranges, int n) {

    if (n != 0) {

       for (int i = 0; i < ranges[n-1]; i++) {

          indices.push(i);
          recursiveFor(indices, ranges, n-1);
          indices.pop();
       }
    }

    else {

       // inner most loop body, access to the index values thru indices
       System.out.println(indices);
    }
}

Пример вызова:

int[] ranges = {2, 2, 2};

recursiveFor(new ArrayDeque<Integer>(), ranges, ranges.length);

я впервые отвечаю на вопрос, но мне показалось, что мне нужно поделиться этой информацией из `

for (x = 0; x < base; ++x) {
  for (y = 0; y < loop; ++y) {
      DoSomething();
  }
}

эквивалентно

for (x = 0; x < base*loop; ++x){
    DoSomething();
}

так что, если вам нужно n гнезд, это можно записать с использованием деления между base и loop, чтобы это выглядело так просто:

char[] numbs = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
     public void printer(int base, int loop){
       for (int i = 0; i < pow(base, loop); i++){
         int remain = i;
         for (int j = loop-1; j >= 0; j--){
           int digit = remain/int(pow(base, j));
           print(numbs[digit]);
           remain -= digit*pow(base, j);
         }
         println();
       }
     }

поэтому, если вы введете printer(10, 2);, он выведет:

00
01
02
03
04
...
97
98
99

Это сработало для меня очень хорошо - мне пришлось выбирать из некоторых альтернатив, которые были сохранены в myAlternativePaths, и основная идея заключается в том, что я пытался создать следующий выбор, и когда произошел " overflow Quot; в одном измерении / компоненте вы просто повторно инициализируете это измерение и добавляете одно к следующему.

public boolean isValidAlternativeSelection (int[] alternativesSelected) {
    boolean allOK = true;
    int nPaths= myAlternativePaths.size();
    for (int i=0; i<nPaths; i++) {
        allOK=allOK & (alternativesSelected[i]<myAlternativePaths.get(i).myAlternativeRoutes.size());
    }
    return allOK;
}


public boolean getNextValidAlternativeSelection (int[] alternativesSelected) {
    boolean allOK = true;
    int nPaths= myAlternativePaths.size();
    alternativesSelected[0]=alternativesSelected[0]+1;
    for (int i=0; i<nPaths; i++) {
        if (alternativesSelected[i]>=myAlternativePaths.get(i).myAlternativeRoutes.size()) {
            alternativesSelected[i]=0;
            if(i<nPaths-1) {
                alternativesSelected[i+1]=alternativesSelected[i+1]+1;
            } else {
                allOK = false;
            }
        }
 //       allOK=allOK & (alternativesSelected[i]<myAlternativePaths.get(i).myAlternativeRoutes.size());
    }
    return allOK;
}

В целях краткости я помещаю свой код здесь:

void variDepth(int depth, int n, int i) {
    cout<<"\n d = "<<depth<<" i = "<<i;
    if(!--depth) return;
    for(int i = 0;i<n;++i){
        variDepth(depth,n,i);
    }
}
void testVariDeapth()
{   variDeapth(3, 2,0);
}

Выход

 d = 3 i = 0
 d = 2 i = 0
 d = 1 i = 0
 d = 1 i = 1
 d = 2 i = 1
 d = 1 i = 0
 d = 1 i = 1
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top