Как я могу запрограммировать большое количество циклов - PullRequest
2 голосов
/ 05 июня 2011

Я новичок в программировании, поэтому прошу прощения, если я не правильно задаю этот вопрос.

У меня есть следующий код:

int sum = 100;
int a1 = 20;
int a2 = 5;
int a3 = 10;
for (int i = 0; i * a1 <= sum; i++) {
    for (int j = 0; i * a1 + j * a2 <= sum; j++) {
        for (int k = 0; i * a1 + j * a2 + k * a3 <= sum; k++) {
            if (i * a1 + j * a2 + k * a3 == sum) {
                System.out.println(i + "," + j + "," + k);
            }
        }
    }   
}

По сути, он сообщает мне различные комбинации a1, a2 и a3, которые равны вышеуказанной сумме (в данном случае 100). Это работает нормально, но я пытаюсь применить его к большему набору данных сейчас, и я не уверен, как обойтись без ручного программирования циклов for или знания заранее, сколько у меня будет переменных (может быть от 10 до 6000 ). У меня в основном есть SQL-запрос, который загружает эти данные из массива.

Есть ли способ в Java OR python (я изучаю оба) для автоматического создания вложенных циклов for и if?

Заранее большое спасибо.

Ответы [ 5 ]

13 голосов
/ 05 июня 2011

рекурсия.

Это звучит так, как будто вы пытаетесь решить:

Ваш текущий пример: 20x 1 + 5x 2 + 10x 3 = 100

В общем, вы делаете: A 1 x 1 + A 2 x 2 + ... + A n x n = SUM

так что вы передаете массив констант {A 1 , A 2 , ..., A n } и вы хотите решить для {x 1 , x 2 , ..., x n }

    public void findVariables(int[] constants, int sum, 
                              int[] variables, int n, int result) {
        if (n == constants.length) { //your end condition for the recursion
            if (result == sum) {
                printArrayAsList(variables);
            }
        } else if (result <= sum){ //keep going
            for (int i = 0; result + constants[n]*i <= sum; i++) {
                variables[n] = i;
                findVariables(constants, sum, variables, n+1, result+constants[n]*i);
            }
        }
    }

и для вызова вашего примера вы бы использовали:

    findVariables(new int[] {20, 5, 20}, 100, new int[] {0,0,0}, 0, 0)
7 голосов
/ 05 июня 2011

Несмотря на то, что он может не масштабироваться, вот действительно простое решение Python для грубой силы, которое не требует рекурсии:

import itertools
target_sum = 100
a = 20
b = 5
c = 10
a_range = range(0, target_sum + 1, a)
b_range = range(0, target_sum + 1, b)
c_range = range(0, target_sum + 1, c)
for i, j, k in itertools.product(a_range, b_range, c_range):
    if i + j + k == 100:
        print i, ',', j, ',', k

Кроме того, существуют способы вычисления декартового произведения произвольного списка списков.без рекурсии.(lol = список списков)

def product_gen(*lol):
    indices = [0] * len(lol)
    index_limits = [len(l) - 1 for l in lol]
    while indices < index_limits:
        yield [l[i] for l, i in zip(lol, indices)]
        for n, index in enumerate(indices):
            index += 1
            if index > index_limits[n]:
                indices[n] = 0
            else:
                indices[n] = index
                break
    yield [l[i] for l, i in zip(lol, indices)]

Если вы только изучаете python, вы, возможно, не знакомы с оператором yield или функцией zip;в этом случае приведенный ниже код будет более понятным.

def product(*lol):
    indices = [0] * len(lol)
    index_limits = [len(l) - 1 for l in lol]
    index_accumulator = []
    while indices < index_limits:
        index_accumulator.append([lol[i][indices[i]] for i in range(len(lol))])
        for n, index in enumerate(indices):
            index += 1
            if index > index_limits[n]:
                indices[n] = 0
            else:
                indices[n] = index
                break
    index_accumulator.append([lol[i][indices[i]] for i in range(len(lol))])
    return index_accumulator

Вы сделали умную вещь в своем коде, пропустив те значения, для которых i + j + k больше sum.Ни один из них не делает этого.Но для этого можно изменить вторые два с некоторой потерей общности.

3 голосов
/ 05 июня 2011

В Java для некоторой общей упрощенной реализации потребуется по крайней мере два класса:

Некоторый делегат для перехода к рекурсивному алгоритму, чтобы вы могли получать обновления, где бы ни находилось выполнение.Что-то вроде:

public interface IDelegate {
   public void found(List<CombinationFinder.FoundElement> nstack);
}

Реализация для , что-то вроде:

public class CombinationFinder {
   private CombinationFinder next;
   private int multiplier;

   public CombinationFinder(int multiplier) {
      this(multiplier, null);
   }
   public CombinationFinder(int multiplier, CombinationFinder next) {
      this.multiplier = multiplier;
      this.next = next;
   }

   public void setNext(CombinationFinder next) {
      this.next = next;
   }

   public CombinationFinder getNext() {
      return next;
   }

   public void search(int max, IDelegate d) {
      Stack<FoundElement> stack = new Stack<FoundElement>();
      this.search(0, max, stack, d);
   }

   private void search(int start, int max, Stack<FoundElement> s, IDelegate d) {
      for (int i=0, val; (val = start + (i*multiplier)) <= max; i++) {
         s.push(i);
         if (null != next) {
            next.search(val, max, s, d);
         } else if (val == max) {
            d.found(s);
         } 
         s.pop();
      }
   } 

   static public class FoundElement {
      private int value;
      private int multiplier;
      public FoundElement(int value, int multiplier) {
         this.value = value;
         this.multiplier = multiplier;
      }
      public int getValue() {
         return value;
      }
      public int getMultiplier() {
         return multiplier;
      }
      public String toString() {
         return value+"*"+multiplier;
      }
   }
}

И, наконец, для настройки и запуска (тестирования):

CombinationFinder a1 = new CombinationFinder(20);
CombinationFinder a2 = new CombinationFinder(5);
CombinationFinder a3 = new CombinationFinder(10);

a1.setNext(a2);
a2.setNext(a3);

a1.search(100, new IDelegate() {
   int count = 1;
   @Override
   public void found(List<CombinationFinder.FoundElement> nstack) {
      System.out.print("#" + (count++) + " Found : ");
      for (int i=0; i<nstack.size(); i++) {
         if (i>0) System.out.print(" + ");
            System.out.print(nstack.get(i));
         }
         System.out.println();
      }
   }
});

Выводит 36 решений.

С помощью этой концепции вы можете иметь столько внутренних циклов, сколько хотите, и даже настраивать каждый из них, если хотите через наследование.Вы можете даже без проблем использовать объекты (например: a1.setNext(a1);).

** ОБНОВЛЕНИЕ **

Просто потому, что мне нравится monty *Решение 1028 * , я не смог устоять перед его тестированием, и вот результат, немного подправленный.

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ всекредиты идут на Монти для алгоритма

public class PolynomialSolver {

   private SolverResult delegate;
   private int min = 0;
   private int max = Integer.MAX_VALUE;

   public PolynomialSolver(SolverResult delegate) {
      this.delegate = delegate;
   }

   public SolverResult getDelegate() {
      return delegate;
   }

   public int getMax() {
      return max;
   }

   public int getMin() {
      return min;
   }

   public void setRange(int min, int max) {
      this.min = min;
      this.max = max;
   }

   public void solve(int[] constants, int total) {
      solveImpl(constants, new int[constants.length], total, 0, 0);
   }

   private void solveImpl(int[] c, int[] v, int t, int n, int r) {
      if (n == c.length) { //your end condition for the recursion
         if (r == t) {
            delegate.solution(c, v, t);
         }
      } else if (r <= t){ //keep going
         for (int i=min, j; (i<=max) && ((j=r+c[n]*i)<=t); i++) {
            v[n] = i;
            solveImpl(c, v, t, n+1, j);
         }
      }
   }

   static public interface SolverResult {
      public void solution(int[] constants, int[] variables, int total);
   }

   static public void main(String...args) {

      PolynomialSolver solver = new PolynomialSolver(new SolverResult() {
         int count = 1;
         @Override
         public void solution(int[] constants, int[] variables, int total) {
            System.out.print("#"+(count++)+" Found : ");
            for (int i=0, len=constants.length; i<len; i++) {
               if (i>0) System.out.print(" + ");
               System.out.print(constants[i]+"*"+variables[i]);
            }
            System.out.println(" = " + total);
         }
      });

      // test some constants = total
      solver.setRange(-10, 20);
      solver.solve(new int[] {20, 5, 10}, 100); // will output 162 solutions

   }
}
2 голосов
/ 05 июня 2011

Основано на решении @ monty, но с несколькими изменениями.Последняя константа может быть определена делением.

public static void findVariables(int[] constants, int sum) {
    findVariables0(constants, sum, new int[constants.length], 0);
}

private static void findVariables0(int[] constants, int remaining, int[] variables, int n) {
    if(n == constants.length - 1) {
        // solution if the remaining is divisible by the last constant.
        if (remaining % constants[n] == 0) {
            variables[n] = remaining/constants[n];
            System.out.println(Arrays.toString(variables));
        }
    } else {
        for (int i = 0, limit = remaining/constants[n]; i <= limit; i++) {
            variables[n] = i;
            findVariables0(constants, remaining - i * constants[n], variables, n+1);
        }
    }
}

public static void main(String... args) {
    findVariables(new int[] { 5,3,2 }, 100);
}

Когда я изменил int на double, я бы не стал использовать float в 99% случаев из-за ошибки округления, которую выget не стоит памяти, которую вы сохраняете.

public static void findVariables(double[] constants, double sum) {
    findVariables0(constants, sum, new double[constants.length], 0);
}

private static void findVariables0(double[] constants, double remaining, double[] variables, int n) {
    if(n == constants.length - 1) {
        // solution if the remaining is divisible by the last constant.
        if (remaining % constants[n] == 0) {
            variables[n] = remaining/constants[n];
            System.out.println(Arrays.toString(variables));
        }
    } else {
        for (int i = 0, limit = (int) (remaining/constants[n]); i <= limit; i++) {
            variables[n] = i;
            findVariables0(constants, remaining - i * constants[n], variables, n+1);
        }
    }
}

public static void main(String... args) {
    findVariables(new double[]{5.5, 3, 2}, 100);
}

Компилируется и работает нормально.

2 голосов
/ 05 июня 2011

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

Есть несколько статей о том, как решать диофантовы уравнения более эффективно.Теория чисел не является моей областью знаний, но, надеюсь, она поможет.

http://www.wikihow.com/Solve-a-Linear-Diophantine-Equation

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...