【问题标题】:how can I program a large number of for loops如何编写大量的 for 循环
【发布时间】:2011-09-08 14:32:15
【问题描述】:

我是编程新手,所以如果我没有正确地问这个问题,我很抱歉措辞。

我有以下代码:

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

基本上它的作用是告诉我a1a2a3 的不同组合,它们等于上述总和(在本例中为 100)。这工作正常,但我现在正试图将它应用到更大的数据集,我不确定如何不手动编程 for 循环或预先知道我将拥有多少个变量(可能是 10 到 6000 )。我基本上有一个从数组加载数据的 sql 查询。

在 Java 或 python(我都在学习)中有没有办法自动创建嵌套的 forif 循环?

非常感谢。

【问题讨论】:

  • 听起来很可疑,你应该在 SQL 查询中做一些这样的工作......
  • “自动创建嵌套循环”...你的意思是递归?
  • 是的,按照 Mitch 所说的去做,您可能不必直接处理 6000 个变量。
  • 我不确定这是否适合我..因为一旦我创建了它,我需要分析每个结果。我使用 SQL 将数据数组拉下,但不确定它将如何帮助我(我正在做的称为丢番图方程),因为数组中的所有值都在递归中进行分析(感谢 JBernardo)。
  • 听起来像全球变暖,N ?= NP。在您的示例中,所有总和都包含 5 的倍数(10=2*5, 20=4*5),因此您可以将 100 除以 5(即 20),然后仅将其中的一些五合并为 10 和 20ies。你可以避免内部 if (这不是一个循环,顺便说一句。)而是改变最里面的循环,从 sum 中减去 ia1 + ja2,然后检查,如果这是可被 a3 整除,但这对 6000 个变量没有多大帮助。 :)

标签: java python algorithm math loops


【解决方案1】:

可能有一种方法可以将变量放入 List 并使用递归来消除所有循环。然而,这种蛮力方法的运行时间随着变量的数量呈指数增长。 (即,对于成千上万的变量,该算法可能无法在我们的一生中完成)。

有一些关于如何更有效地求解丢番图方程的文章。数论不是我的专业领域,但希望这些能有所帮助。

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

【讨论】:

  • 感谢 Btreat 抽出宝贵时间回答。您发布的链接似乎只使用 2 个变量,我可以使用 3 个或更多变量来完成,但我的问题是我手动创建循环,我想以某种方式自动化解决丢番图方程的蛮力尝试。 (也不担心缺乏数论专业知识..我是一个新手,学习数论和编程:-)
  • 问题在于,对于丢番图方程 a_1 + a_2 + ... = k,您真的无能为力。没有系数,没有辅助的数论属性等。
  • @ShreevatsaR 你是对的。我相信解决丢番图方程在数学/计算上等同于找到大量的所有素数。如果我没记错的话,大部分密码学都依赖于无法有效地做到这一点。
  • 哦,但是当数字很小的时候没关系。您可以将 n 分解为 O(√n) 时间等。因此,只要 n
【解决方案2】:

递归。

这听起来像是您要解决的问题:

您当前的示例:20x1 + 5x2 + 10x3 = 100

所以一般来说你在做: A1x1 + A2x2 + .. . + Anxn = SUM

所以你传入一个常量数组 {A1, A2, ..., An} 你想解决 {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);
            }
        }
    }

并调用您将使用的示例:

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

【讨论】:

  • 我已经对其进行了测试并修复了错误。 :-)
  • 顺便说一句,您实际上并不需要result:您可以在每次调用中传递sum-constants[n]*isum 现在表示要实现的 剩余 总和) , 并在它变为 0 时进行测试。此外,在 else 上不需要条件:因为 for 循环检查 result 不会变得大于 sum(或 @如果您取消result,987654329@ 不会变成负数)。这将是你的函数的一个少论点。不过,我不想对您的代码进行如此彻底的更改。
  • +1 表示聪明,尽管我认为让这个方法私有和另一个只有两个参数(常量、总和)的公共方法会更好。
  • 太棒了,非常感谢蒙蒂..我遇到了一个小问题,虽然我无法让 printArrayAsList 工作(我查了一下,没有找到任何对它的引用..)是吗服装命令还是我需要导入一些东西才能让它工作?
【解决方案3】:

使用 Java,一些通用的简单实现至少需要两个类:

一些委托传递给递归算法,因此无论执行在哪里,您都可以接收更新。类似的东西:

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

for 实现,类似于:

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

** 更新 **

仅仅因为我喜欢montysolution,我忍不住测试了它,结果如下,稍作调整。

免责声明算法的所有功劳归monty

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

   }
}

【讨论】:

  • 感谢 ShreevatsaR,我的 ìf 声明中有一个错误。我修好了,再加上把条件反过来,所以结果显示了整个“公式”,包括0的
  • 感谢亚尼克的回答。我测试了你的代码,但我在调整它时遇到了麻烦。我想包括小数而不是 int,但是当我把所有东西都变成浮点数时,它会保持错误的 sys 类型,它似乎要求双精度数。我会继续插电,但任何建议都会很棒。
  • @Lostsoul,在 Java 中,0.4 之类的值默认为 double。就像5int。要拥有short,您需要对其进行转换。要拥有float,您可以转换它,或使用0.5f 表示法。
  • 我不知道 Yanick ..我一直认为 float 是小数。我会转换所有整数和双打,看看情况如何......谢谢你清理它。
  • @Lostsoul,另外,在处理浮点值时,您需要考虑0.1f * 0.1f = 0.010000001f0.1d * 0.1d = 0.010000000000000002,因此您不能只将等式结果与等号(@ 987654340@) 而是,您需要比较结果的差异是否在 epsilon 值(读取 en.wikipedia.org/wiki/Machine_epsilon)或容错值内。
【解决方案4】:

虽然它可能无法扩展,但这里有一个非常简单的蛮力 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 的值。这些都没有这样做。但是可以修改后两个来做到这一点,但会失去一般性。

【讨论】:

    【解决方案5】:

    基于@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 时,我不会在 99% 的情况下使用 float,因为您得到的舍入误差不值得您节省的内存。

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

    这编译并运行良好。

    【讨论】:

    • 感谢 Peter,我编译了您的代码并对其进行了测试,效果很好..但我也想尝试获取小数并将所有 int 转换为浮点数,但出现此错误:无法编译的源代码- 可能需要的精度损失:发现浮点数:双
    • 当我将其更改为浮点数时,结果显示小数,但是当我将 findVariables 更改为在常量或总和中包含小数时,我收到上述错误
    • 我猜你没有正确使用演员表??当我转换代码时,它编译并运行良好。
    • 非常感谢彼得..我修复了它..我是 java 新手,当我认为我需要小数时正在使用浮点数..不知道它是我实际需要的两倍..它现在完美运行..感谢一百万!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-24
    • 2010-09-08
    • 2022-01-27
    • 1970-01-01
    • 1970-01-01
    • 2016-07-05
    相关资源
    最近更新 更多