如何清理我的代码

时间:2010-06-14 13:52:48

标签: java refactoring

对此我是新手,我真的想学习如何保持代码尽可能简单,同时完成应有的工作。

我所做的问题来自Project Euler,它说

  

Fibonacci序列中的每个新术语都是通过添加   前两个任期。从1开始   2,前10个术语将是:

     
1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...
  
     

查找序列中所有偶数值的总和   不超过四百万。

以下是我的代码。我想知道简化这个的最佳方法是什么,开始删除所有的.get(list.length() - 1).....如果可能的话,这将是一个好的开始,但我不是真的知道怎么做?

由于

public long fibb()
{
    ArrayList<Integer> list = new ArrayList<Integer>();


    list.add(1);
    list.add(2);

    while((list.get(list.size() - 1) +  (list.get(list.size() - 2)) < 4000000)){  
        list.add((list.get(list.size()-1)) + (list.get(list.size() - 2)));
    }     

    long value = 0;
    for(int i = 0; i < list.size(); i++){
        if(list.get(i) % 2 == 0){
            value += list.get(i);
        }    
    }
    return value;
}

10 个答案:

答案 0 :(得分:33)

其他响应者都给出了很好的答案。我想向您展示重构如何在行动中起作用,不仅仅是针对这个特定问题,了解有关斐波那契数字的事情,而是作为一个迭代过程,小心地将代码简化为最低限度。重构让我们从工作但复杂的代码开始,一步一步地逐步削减它。让我向您展示在完成最终解决方案的过程中您可以采取的所有步骤。

注意:我已将初始起始值更改为1和1而不是1和2.严格来说,Fibonacci序列以两个1开头,如1,1,2,3,5 ......

第1步 - 撤销列表

对于初学者来说,要摆脱重复的list.size() - x表达式,您可以按 reverse 顺序添加数字。然后找到最近的两个数字就更简单了。

public long fibb()
{
    ArrayList<Integer> list = new ArrayList<Integer>();

    list.add(1);
    list.add(1);

    while (list.get(0) + list.get(1) < 4000000) {
        // Use list.add(0, ...) to add entries to the *front*.
        list.add(0, list.get(0) + list.get(1));
    }     

    long value = 0;

    for (int i = 0; i < list.size(); i++) {
        if (list.get(i) % 2 == 0) {
            value += list.get(i);
        }    
    }

    return value;
}

第2步 - 切换到链接列表

我们将ArrayList切换为LinkedList。在数组的开头插入是低效的,而在链表上快速操作。

沿着这些方向,我们需要摆脱第二个循环中的get()调用。使用链接列表按索引查找条目很慢。为此,我已将第二个循环更改为使用for (variable: container)语法。

public long fibb()
{
    // Changed to use a linked list for faster insertions.
    List<Integer> list = new LinkedList<Integer>();

    list.add(1);
    list.add(1);

    // Using get() is normally a bad idea on linked lists, but we can get away
    // with get(0) and get(1) since those indexes are small.
    while (list.get(0) + list.get(1) < 4000000) {
        list.add(0, list.get(0) + list.get(1));
    }     

    long value = 0;

    // Altered loop to avoid expensive get(i) calls.
    for (Integer n: list) {
        if (n % 2 == 0) {
            value += n;
        }    
    }

    return value;
}

第3步 - 结合循环

下一个优化是组合两个循环。您可以在生成偶数时检查偶数,而不是先生成所有数字,然后再检查偶数。

public long fibb()
{
    List<Integer> list = new LinkedList<Integer>();
    long value = 0;

    list.add(1);
    list.add(1);

    while (list.get(0) + list.get(1) < 4000000) {
        int next = list.get(0) + list.get(1);

        list.add(0, next);

        if (next % 2 == 0) {
            value += next;
        }    
    }     

    return value;
}

第4步 - 删除列表

现在您可能会注意到,您从未引用过索引1之外的数字。位置2及以后的数字永远不会再次使用。这暗示您甚至不需要再保留所有数字的列表。由于您在生成偶数时会检查它们,因此现在可以丢弃除最近两个数字之外的所有数字。

此外,作为次要细节,我们将value重命名为total

public long fibb()
{
    int a = 1, b = 1;
    long total = 0;

    while (a + b < 4000000) {
        // Calculate the next number.
        int c = a + b;

        // Check if it's even.
        if (c % 2 == 0) {
            total += c;
        }

        // Shift the values.
        a = b;
        b = c;
    }     

    return total;
}

答案 1 :(得分:10)

您不需要列表,只需要最后两项。这是一些伪代码,我会把它翻译成你的语言。

f0=1 #pre-last number
f1=1 #last number
while(...) {
    t = f0 + f1
    if (t%2 == 0) # replaces your second loop 
         sum += t 
    f0 = f1
    f1 = t
}

接下来,您可以观察到数字始终是顺序的:

odd, odd, even, odd, odd, even [...]

并进一步优化,如果您需要

答案 2 :(得分:3)

你可以完全摆脱列表。只需将最后两个斐波那契数字保存在两个变量中,然后在循环中计算下一个变量(类似于第一个循环)。

然后在同一循环中保持与条件匹配的所有数字(即均匀且小于百万)的总计。

答案 3 :(得分:3)

首先,在尝试重写任何代码之前,进行单元测试很有用。即使是最明显的变化也会打破意想不到的事情。

其次,要非常小心。您经常会在代码中看到X调用相同的函数可以被一次调用替换,赋值给变量,并使用该变量。

但是,你必须小心这样做。例如,在当前代码中,您无法真正替换list.size(),因为大小会在同一次迭代中的调用之间一直变化。

使您的代码难以理解的主要原因是您在更新列表时引用列表索引。您需要将索引保存到变量,拆分为多行,并可能添加一些注释以使其更具可读性。

答案 4 :(得分:1)

我会删除列表。当你真正需要做的就是迭代序列并保持一个计数变量时,你在这里进行两次迭代。

  • 因此,使用某种for / while循环遍历Fibonacci序列。
  • 如果是偶数,则将其添加到计数变量。

要对序列进行简单的迭代,您只需要记录两个最近的值( f(n) f(n-1))和(取决于您的语言)更新这些值时的临时变量。通常是:

temp = current;
current = current + previous; // temp holds old value of current so the next line works.
previous = temp;

简单方法是循环的基本方法。或者你可以推出你自己的实现Iterator<E>接口的类,如果你想成为它的全部OO。然后你的代码可以简单:

Fib fib = new Fib();
for(long n : fib) {
    if(n % 2 == 0) t += n;
}

这让我想起,有一天我必须再次回到欧拉。

答案 5 :(得分:1)

Python中的一切看起来更漂亮!

def fibb():
    ret = 2 # to take into account the first 2
    fib = [1, 2]

    while True:
        latest = fib[-1] + fib[-2]
        if latest >= 4000000: return ret

        fib.append(latest)
        if latest % 2 == 0: ret += latest

注意:这比其他任何事情更像是一项编程练习。我意识到转移到Python可能不太实际。

编辑,这是更节省内存的无名单方法:

def fibb():
    ret = 0
    f0, f1 = (1, 1)

    while True:
        f0, f1 = (f1, f0 + f1)
        if f1 >= 4000000: return ret
        if f1 % 2 == 0: ret += f1

答案 6 :(得分:0)

我的建议是这样的(我不会提供答案,因为最好自己得出结论)

尝试考虑为什么需要存储序列的值?你的程序将在一个大阵列中存储4000000个整数,是否真的需要这个?您可以使用2个项目并压缩(使用Fibonacci计算)将偶数添加到Total变量。

答案 7 :(得分:0)

如果不保护列表中的所有值,则会进行大量简化。您可以检查它们是否“在运行中”

例如:

int old, current;
old = 1;
current = 1;

long value = 0;

while(current < 4000000) {
 if(current % 2  == 0) value += current;

 int tmp = old + current;
 old = current;
 current = tmp;
}

return value;

答案 8 :(得分:0)

嗯,首先,Fibonacci序列的成员是由之前的前两个值生成的。为什么不在缓冲区中保留两个数字,并测试数字是否均匀。如果是偶数,请将其加到总和中,当达到四百万时停止。

代码:

public int fibEvenSum(int a, int b) {

int sum = 0;
int c = a + b;

while (c <= 4000000) {

if (c % 2 == 0) {
sum += c;
}

//Shift the sequence by 1
a = b;
b = c;

c = a + b;

} //repeat

return sum;
}

答案 9 :(得分:0)

喜欢@John Kugelman的解决方案,但效率更高。这将仅循环1/3次,每个循环更短,分支更少,甚至不需要测试。这利用了每三个纤维值均匀的事实,并且只计算两者之间的值以重置a和b值。

public static long fibb() {
    int a = 1, b = 1;
    long total = 0;
    while (true) {
        int c = a + b;
        if (c >= 4000000) return total;
        total += c;
        a = b + c;
        b = c + a;
    }
}