Другими словами, могу я сделать что-нибудь вроде
for() {
for {
for {
}
}
}
Кроме N раз? Другими словами, когда вызывается метод, создающий циклы, ему дается некоторый параметр N, и тогда метод создает N из этих циклов, вложенных один в другой?
Конечно, идея состоит в том, что должен быть «простой» или «обычный» способ сделать это. У меня уже есть идея очень сложной.
Мне просто любопытно, каков был ваш "сложный" способ создания n-уровневых циклов?




Похоже, вы можете посмотреть рекурсия.
Это все еще не дает ответа на вопрос. Итерация - это своего рода рекурсия. Возможно, вы собираетесь включить пример рекурсии взаимный?
Проблема требует уточнения. Возможно, вам поможет рекурсия, но имейте в виду, что рекурсия почти всегда является альтернативой итерации, и наоборот. Возможно, для ваших нужд будет достаточно двухуровневого вложенного цикла. Просто дайте нам знать, какую проблему вы пытаетесь решить.
Возможно, вы захотите объяснить, чем вы действительно хотите заниматься.
Если внешние циклы for не делают ничего, кроме управления счетчиком, то ваши вложенные циклы for представляют собой просто более сложный способ итерации по счетчику, который может быть обработан одним циклом 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, вложенных друг в друга, без необходимости выяснять, как именно они должны сочетаться друг с другом.
Это типичное использование рекурсии, когда вы просматриваете рекурсивную структуру данных.
jjnguy прав; Рекурсия позволяет динамически создавать вложения переменной глубины. Однако вы не получите доступа к данным из внешних слоев без дополнительной работы. Случай "встроенного вложенного":
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
@Pacerier, да, явная рекурсия происходит в частном методе n_for класса NestedFor. (Я бы, конечно, написал на Java 8 иначе :)
Что это за класс IAction, который вы используете? Я не могу найти то, что мне нужно импортировать, чтобы использовать ...
Это не отдельный класс, это интерфейс, который joel.neely определяет вверху. Затем, что бы вы ни хотели сделать в конце цикла for, вам нужно будет написать отдельный класс, реализующий IAction и имеющий что-то в действии (индексы int []).
Основная идея вложенных циклов - умножение.
Расширяя ответ Майкла Берра, если внешние циклы for не делают ничего, кроме управления счетчиком, то ваши вложенные циклы for по счетчикам n - это просто более сложный способ перебора произведения счетчиков с помощью одного цикла for.
Теперь давайте распространим эту идею на списки. Если вы выполняете итерацию по трем спискам во вложенных циклах, это просто более сложный способ перебора продукта списков с помощью одного цикла. Но как выразить произведение трех списков?
Во-первых, нам нужен способ выражения продукта типов. Продукт двух типов X и Y может быть выражен как общий тип, такой как P2<X, Y>. Это просто значение, состоящее из двух значений: одного типа X, а другого типа Y. Выглядит это так:
public abstract class P2<A, B> {
public abstract A _p1();
public abstract B _p2();
}
Для продукта трех типов у нас есть только P3<A, B, C> с очевидным третьим методом. Таким образом, продукт из трех списков достигается путем распределения функтора List по типу продукта. Итак, продукт List<X>, List<Y> и List<Z> - это просто List<P3<X, Y, Z>>. Затем вы можете перебирать этот список с помощью одного цикла.
Библиотека Функциональная Java имеет тип List, который поддерживает умножение списков вместе с использованием первоклассных функций и типов продуктов (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, вы можете сделать doSomething первоклассным следующим образом. Допустим, doSomething возвращает строку:
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 и собрать результаты всех приложений doSomething:
List<String> s = xs.map(P.p3()).apply(ys).apply(zs).map(doSomething);
Разве это не внутренняя рекурсия?
Я ценю связь с умножением, но кажется, что она разрушится, если мы отойдем от «границ», известных до внешнего уровня. Если есть, например, for (shelf : library) {for (book : shelf) {for (page : book) {...}}}, который имеет иную «форму», чем тот, который возникает в результате образования xs, ys и zs вне всей конструкции.
@ joel.neely Это абсолютно верно, и вы сформулировали разницу между аппликативными функторами (используемыми в этом ответе) и монадами.
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();
}
Создает красивый вложенный скелет цикла for ;-) Не совсем серьезно, и я знаю, что рекурсивное решение было бы более элегантным.
нет, я просто говорю, что вопрос не ясен. Вся проблема также может быть примерно такой: «как мне заставить мою IDE выдать мне n-уровневый шаблон цикла». Мой код довольно далек от генерации кода во время выполнения, он задумывался как генерация кода во время разработки, иначе говоря, шаблоны. Кстати. автор ни разу не сказал, что хочет сразу выполнить петли. Я просто интерпретирую вопрос немного иначе, чем другие люди, ответившие на него до меня.
Вы всегда можете сгенерировать файл perl из кода Java, а затем выполнить его. :) И мы очень серьезно к этому относимся. :)
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);
Для краткости помещаю здесь свой код:
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
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, ...); но необходимо указать to (...).
Файл NForTest должен демонстрировать несколько различных способов его использования.
Основная предпосылка этого состоит в том, чтобы просто продвигать «индексы» каждый ход, а не использовать рекурсию.
Отличный ответ. Я предпочитаю это рекурсии
Это отличный подход, я бы обернул его красивым классом и предоставил обратный вызов, чтобы сделать его более аккуратным ...
Самый простой общий подход, который я мог придумать в 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);
}
}
}
Если у вас есть общая структура вложенного цикла, например:
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");
}
счетчики - это массив размера d, представляющий соответствующие переменные i0,i1,i2,...id соответственно int counters[d].
nestedToRecursion(counters,0);
Точно так же мы можем преобразовать другие переменные, такие как инициализация рекурсии или завершение, используя для них массивы, то есть у нас может быть initial[d], ending[d].
я впервые отвечаю на вопрос, но мне казалось, что мне нужно поделиться этой информацией из `
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, и основная идея заключалась в том, что я пытался построить следующий выбор, и когда было "переполнение" в одном измерении / компоненте, вы просто повторно инициализируйте это измерение и добавьте одно к следующему.
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;
}
Я тоже пытался решить эту проблему и в итоге создал этот простой решение.
Например, предположим, что нам нужно динамически генерировать такие циклы:
for (int i = 0; i < 2; i++) {
for (int j = 1; j < 3; j++) {
for (int k = 2; k < 4; k++) {
System.out.println(Arrays.asList(i, j, k));
}
}
}
Итак, мы можем реализовать это с помощью такого конструктора:
new Loops()
.from(0).to(2)
.from(1).to(3)
.from(2).to(4)
.action(System.out::println);
Результат исполнения:
[0, 1, 2]
[0, 1, 3]
[0, 2, 2]
[0, 2, 3]
[1, 1, 2]
[1, 1, 3]
[1, 2, 2]
[1, 2, 3]
Надеюсь, это будет полезно и для кого-то другого.
Можете ли вы уточнить, какие параметры будут внутри каждого for ()?