开发者

how can I program a large number of for loops

开发者 https://www.devze.com 2023-03-10 23:52 出处:网络
I\'m new to programming so I\'m sorry in phrasing if I\'m not asking this question correctly. I have the following code:

I'm new to programming so I'm sorry in phrasing if I'm not asking this question correctly.

I have the following code:

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 * 开发者_JS百科a3 == sum) {
                System.out.println(i + "," + j + "," + k);
            }
        }
    }   
}

basically what it does is tell me the different combinations of a1, a2, and a3 that equal the above sum (in this case 100). This works fine but I'm trying to apply it to a larger dataset now and I'm not sure how to do without manually programming the for loops or knowing in advanced how many variables I'll have(could be anywhere from 10 to 6000). I basically have a sql query that loads that data from an array.

Is there a way in Java OR python (I'm learning both) to automatically create nested for and if loops?

Thanks so much in advance.


Recursion.

This is what it sounds like you are trying to solve:

your current example: 20x1 + 5x2 + 10x3 = 100

so in general you are doing: A1x1 + A2x2 + ... + Anxn = SUM

so you pass in an array of constants {A1, A2, ..., An} and you want to solve for {x1, x2, ..., xn}

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

and to call for your example you'd use:

    findVariables(new int[] {20, 5, 20}, 100, new int[] {0,0,0}, 0, 0)


Although it may not scale, here's a really simple brute-force python solution that doesn't require recursion:

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

Also, there are ways to compute the cartesian product of an arbitrary list of lists without recursion. (lol = list of lists)

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)]

If you're just learning python, then you might not be familiar with the yield statement or the zip function; in that case, the below code will be clearer.

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

You did a smart thing in your code by skipping those values for which i + j + k is greater than sum. None of these do that. But it's possible to modify the second two to do that, with some loss of generality.


With Java, some generic simplistic implementation would require at least two classes :

Some delegate to pass to the recursive algorithm, so you can receive updates wherever the execution is at. Something like :

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

The for implementation, something like :

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

And finally, to setup and run (test) :

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

Will output 36 solutions.

With this concept, you can have as many inner loops as you want, and even customize each one if you want through inheritance. You can even reuse objects (ie: a1.setNext(a1);) with no problem at all.

** UPDATE **

Simply because I like monty's solution, I couldn't resist into testing it, and here's the result, tweaked a little.

DISCLAIMER all credits goes to monty for the algorithm

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

   }
}


There may be a way to put the variables into a List and use recursion to eliminate all of the loops. However, the running time of this brute force approach grows exponentially with the number of variables. (i.e. the algorithm may not complete in our lifetimes for numbers of variables in the thousands).

There are some articles on how to solve Diophantine equations more efficiently. Number theory is not my area of expertise, but hopefully these will be of assistance.

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


Based on @monty's solution but with a few tweaks. The last constant can be determined by division.

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

When I changed int to double I wouldn't use float for 99% of cases due to the rounding error you get is not worth the memory you save.

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

This compiles and runs fine.

0

精彩评论

暂无评论...
验证码 换一张
取 消