如何更快地运行此代码?

时间:2013-11-12 14:47:51

标签: java performance

import java.io.*;
import java.util.ArrayList;

public class Ristsumma {
static long numberFromFile;
static long sum1, sum2;
static long number, number2;
static long variable, variable2;
static long counter;

public static void main(String args[]) throws IOException{
    try{
        BufferedReader br = new BufferedReader(new FileReader("ristsis.txt"));
        numberFromFile = Long.parseLong(br.readLine()); 

        br.close();
    }catch(Exception e){
    e.printStackTrace();
    }   

    variable=numberFromFile;
    ArrayList<Long> numbers = new ArrayList<Long>();

    while (variable > 0){
        number = variable %10;
        variable/=10;
        numbers.add(number);
    }

    for (int i=0; i< numbers.size(); i++) {
        sum1 += numbers.get(i);
    }

    ArrayList<Long> numbers2 = new ArrayList<Long>();
    for(long s=1; s<numberFromFile; s++){
        variable2=s;
        number2=0;
        sum2=0;

        while (variable2 > 0){
            number2 = variable2 %10;
            variable2/=10;  
            numbers2.add(number2);
        }

        for (int i=0; i< numbers2.size(); i++) {
            sum2 += numbers2.get(i);
        }

        if(sum1==sum2){
        counter+=1; 
        }

        numbers2.clear();
    }       
PrintWriter pw = new PrintWriter("ristval.txt", "UTF-8");
pw.println(counter);
pw.close();

}
}

所以我有这个代码。它从文件中取一个数字,将所有数字与该数字分开添加并将它们加在一起(例如,数字为123,然后它给出1 + 2 + 3 = 6)。在下半部分,它会查找文件中从1到该数字的所有数字,并计算有多少不同的数字给出相同的答案。如果数字是123,则总和为6,代码写入的答案为9(因为6,15,24,33,42,51,60,105,114也给出相同的答案)。代码有效,但我的问题是,当文件中的数字是例如2 222 222 222时,则需要将近半小时才能得到答案。如何让这个运行更快?

4 个答案:

答案 0 :(得分:3)

删除不必要的列表创建

您不必要地创建列表

ArrayList<Long> numbers = new ArrayList<Long>();

while (variable > 0){
    number = variable %10;
    variable/=10;
    numbers.add(number);
}

for (int i=0; i< numbers.size(); i++) {
    sum1 += numbers.get(i);
}

在这里你创建一个arraylist,只是暂时持有Longs,你可以删除整个列表

while (variable > 0){
    number = variable %10;
    variable/=10;
    sum1 += number
}

其他arraylist numbers2

也是如此

预定集会主义者

我们已经淘汰了arraylists,但如果我们没有,我们可以通过预设阵列来提高速度

ArrayList<Long> numbers = new ArrayList<Long>(someGuessAsToSize);

你的猜测是正确的并不是必须的,arraylist仍会自动调整大小,但如果猜测大致正确,你将加速代码,因为arraylist不必定期调整大小。

一般风格

你持有很多(应该是)方法变量作为字段

static long numberFromFile;
static long sum1, sum2;
static long number, number2;
static long variable, variable2;
static long counter;

这不太可能影响性能,但这是一件不寻常的事情,并且使代码的可读性降低,并且潜在的“隐藏效果”

答案 1 :(得分:2)

你的问题很有趣 - 它让我想知道它会用线程运行多快。

这是一个线程实现,它分割了跨线程计算第二个问题的任务。我的笔记本电脑只有两个内核,所以我将线程设置为4。

public static void main(String[] args) throws Exception {
    final long in = 222222222;
    final long target = calcSum(in);
    final ExecutorService executorService = Executors.newFixedThreadPool(4);
    final Collection<Future<Integer>> futures = Lists.newLinkedList();
    final int chunk = 100;
    for (long i = in; i > 0; i -= chunk) {
        futures.add(executorService.submit(new Counter(i > chunk ? i - chunk : 0, i, target)));
    }
    long res = 0;
    for (final Future<Integer> f : futures) {
        res += f.get();
    }
    System.out.println(res);
    executorService.shutdown();
    executorService.awaitTermination(1, TimeUnit.DAYS);
}

public static final class Counter implements Callable<Integer> {

    private final long start;
    private final long end;
    private final long target;

    public Counter(long start, long end, long target) {
        this.start = start;
        this.end = end;
        this.target = target;
    }

    @Override
    public Integer call() throws Exception {
        int count = 0;
        for (long i = start; i < end; ++i) {
            if (calcSum(i) == target) {
                ++count;
            }
        }
        return count;
    }
}

public static long calcSum(long num) {
    long sum = 0;
    while (num > 0) {
        sum += num % 10;
        num /= 10;
    }
    return sum;
}

它会在几秒钟内以222 222 222作为输入计算解决方案。

我优化了sum的计算,以删除您正在使用的所有List

修改

我使用Stopwatch添加了一些计时代码,并使用222222222 * 100作为输入数字,尝试使用和不使用@ Ingo进行优化。

如果没有优化,代码需要35秒。将calc方法更改为:

public static long calcSum(long num, final long limit) {
    long sum = 0;
    while (num > 0) {
        sum += num % 10;
        if (limit > 0 && sum > limit) {
            break;
        }
        num /= 10;
    }
    return sum;
}

添加优化后,代码需要28秒。

请注意,这是一个非常非科学的基准,因为我没有对JIT进行温暖或进行多次试验(部分原因是因为我很懒,部分是因为我很忙)。

修改

摆弄块大小也会产生相当不同的结果。大约1000次下降到大约17秒。

修改

如果你想真正喜欢,你可以使用ForkJoinPool

public static void main(String[] args) throws Exception {
    final long in = 222222222;
    final long target = calcSum(in);
    final ForkJoinPool forkJoinPool = new ForkJoinPool();
    final ForkJoinTask<Integer> result = forkJoinPool.submit(new Counter(0, in, target));
    System.out.println(result.get());
    forkJoinPool.shutdown();
    forkJoinPool.awaitTermination(1, TimeUnit.DAYS);
}

public static final class Counter extends RecursiveTask<Integer> {

    private static final long THRESHOLD = 1000;
    private final long start;
    private final long end;
    private final long target;

    public Counter(long start, long end, long target) {
        this.start = start;
        this.end = end;
        this.target = target;
    }

    @Override
    protected Integer compute() {
        if (end - start < 1000) {
            return computeDirectly();
        }
        long mid = start + (end - start) / 2;
        final Counter low = new Counter(start, mid, target);
        final Counter high = new Counter(mid, end, target);
        low.fork();
        final int highResult = high.compute();
        final int lowResult = low.join();
        return highResult + lowResult;
    }

    private Integer computeDirectly() {
        int count = 0;
        for (long i = start; i < end; ++i) {
            if (calcSum(i) == target) {
                ++count;
            }
        }
        return count;
    }
}

public static long calcSum(long num) {
    long sum = 0;
    while (num > 0) {
        sum += num % 10;
        num /= 10;
    }
    return sum;
}

在另一台(速度快得多)的计算机上运行时间不到一秒,而原始方法则为2.8秒。

答案 2 :(得分:1)

注意您根本不需要存储个别数字。

相反,你感兴趣的只是数字的实际总和。

考虑到这一点,像

这样的方法
static int diagsum(long number)  { ... }

会很棒。如果它很简单,JIT可以内联它,或者至少比你的意大利面条代码更好地优化。

然后,您可以从另一种方法中受益,该方法可以在某个限度内停止计算数字和。例如,当你有

22222222

总和为20,这意味着您无需计算任何大于20的其他总和。例如:

45678993

相反,你可以在你拥有最后3位数(你通过你的diision方法得到的数字)之后停止,因为9 + 9 + 3是21并且这已经大于20。

=============================================== ====================

另一个优化:

如果你有一些号码:

123116

很明显,这6个数字的所有唯一排列具有相同的数字总和,因此

321611, 231611, ... are solutions

然后,对于任何一对单独的数字ab,转换后的数字将包含(a + 1)(b-1)和(a-1)(b + 1)在同一个地方,只要a + 1 ,...仍然在0..9范围内。递归应用以获得更多的数字。

然后您可以转到数字较少的数字。显然,要具有相同的数字总和,如果可能,您必须组合原始数字的2位数,例如

5412 => 912, 642, 741, 552, 561, 543

等。 如上所述递归地应用相同的算法,直到不可能进行转换和组合。

======

必须要说的是,上述想法需要大量内存,因为必须保持类似Set的数据结构来处理重复。但是,对于987_654_321,我们已经获得了39_541_589个结果,并且可能更多的数字更多。因此,以组合方式实际做到这一点的努力是值得的,这是值得怀疑的。

答案 3 :(得分:1)

您大部分时间都在检查未通过测试的号码。然而,正如Ingo所观察到的,如果你有一个数字ab,那么(a-1)(b + 1)与ab的总和相同。您可以生成它们,而不是检查所有数字:

让我们说我们的数字是2 222,总和是8。 方法#1:自下而上

我们现在生成从最小的开始的数字(我们用零填充以便于阅读):0008。下一个是0017,下一个是0026,0035,0044,0053,0062,0071,0080,0107等等上。有问题的部分是找到第一个有此总和的数字。

方法#2:自上而下

我们从2222开始,下一个较低的数字是2213,然后是2204,2150,2141,依此类推。在这里,您没有需要找到最小数字的问题。

我现在没有时间编写代码,但是应该有一种算法来实现这两种方法,而不涉及尝试所有数字。

对于数字abc,(a)(b-1)(c + 1)是下一个较低的数字,而(a)(b + 1)(c-1)是下一个较高的数字。唯一有趣/困难的事情是当你需要溢出因为b == 9或c == 9,或b == 0,c == 0。如果b == 9则下一个更大的数字是(a + 1)(9)(c-1)如果c> 0,并且(a)(8)(0)如果c == 0。现在去制作你的算法,这些例子应该足够了。