简体   繁体   English

如何清理我的代码

[英]How to clean up my code

Being new to this I really am trying to learn how to keep code as simple as possible, whilst doing the job it's supposed to. 作为新手,我确实在尝试学习如何使代码尽可能简单,同时完成应有的工作。

The question I have done is from Project Euler , it says 我的问题是来自Euler项目 ,它说

Each new term in the Fibonacci sequence is generated by adding the previous two terms. 斐波那契数列中的每个新项都是通过将前两个项相加而生成的。 By starting with 1 and 2, the first 10 terms will be: 从1和2开始,前10个术语将是:

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

Find the sum of all the even-valued terms in the sequence which do not exceed four million. 找出序列中所有不超过400万的偶数项之和。

Here is my code below. 这是下面的代码。 I was wondering what the best way of simplifying this would be, for a start removing all of the .get(list.length()-1 )..... stuff would be a good start if possible but I don't really know how to? 我想知道简化此操作的最佳方法是什么,对于一个开始删除所有.get(list.length()-1).....的东西,如果可能的话将是一个不错的开始,但我不是真的知道怎么?

Thanks 谢谢

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

The other responders have all given great answers. 其他响应者都给出了很好的答案。 I want to show you how refactoring works in action, not just for this specific problem knowing things about Fibonacci numbers, but as an iterative process that carefully whittles down code to the bare minimum. 我想向您展示重构是如何工作的,不仅是针对这个特定问题(了解斐波那契数),而且是一个仔细地将代码缩减到最低限度的迭代过程。 Refactoring lets us start with working but complicated code and steadily pare it down one step at a time. 重构使我们从工作但复杂的代码开始,并一次将其稳定地减少一步。 Let me show you all of the in between steps you could make while working your way towards the final solution. 让我向您展示在朝着最终解决方案努力的过程中,您可以执行的所有步骤。

Note: I've changed your initial starting values to 1 and 1 instead of 1 and 2. Strictly speaking, the Fibonacci sequence begins with two 1's, as in 1, 1, 2, 3, 5... 注意:我已将您的初始起始值更改为1和1,而不是1和2。严格来说,斐波那契数列以两个1开头,如1、1、2、3、5 ...

Step 1 - Reverse the list 步骤1-颠倒清单

For starters, to get rid of the repetitive list.size() - x expressions you could add the numbers in reverse order. 对于初学者来说,要摆脱重复的list.size() - x表达式,可以按相反的顺序添加数字。 Then finding the two most recent numbers is simpler. 然后,找到两个最近的数字会更简单。

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

Step 2 - Switch to a linked list 第2步-切换到链表

Let's switch the ArrayList to a LinkedList . 让我们将ArrayList切换到LinkedList Inserting at the beginning of an array is inefficient, whereas its a quick operation on a linked list. 在数组的开头插入效率不高,而对链表的快速操作是无效的。

Along those lines, we'll need to get rid of the get() calls in the second loop. 沿着这些思路,我们需要摆脱第二个循环中的get()调用。 Looking up entries by index is slow using linked lists. 使用链接列表按索引查找条目很慢。 To do this I've changed the second loop to use for (variable: container) syntax. 为此,我更改了第二个循环以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;
}

Step 3 - Combine the loops 第3步-组合循环

The next optimization is to combine the two loops. 下一个优化是结合两个循环。 Instead of generating all of the numbers first and then checking for even ones later, you could check for even numbers as you generate them. 不必先生成所有数字,然后再检查偶数,而是可以在生成它们时检查偶数。

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

Step 4 - Eliminate the list 步骤4-消除清单

Now you might notice that you never refer to numbers beyond index 1. Numbers at positions 2 and beyond are never used again. 现在您可能会注意到,您永远不会引用索引1以外的数字。位置2及更高位置的数字再也不会被使用。 This hints that you don't even need to keep a list of all the numbers any more. 这表明您甚至不需要再保留所有数字的列表。 Since you are checking for the even numbers as they are generated, you can now throw away all but the two most recent numbers. 由于您要检查生成的偶数,因此现在可以丢弃除两个最近的数字以外的所有数字。

Also, as a minor detail, let's rename value to total . 另外,作为次要细节,让我们将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;
}

You don't need a list, you only need two last items. 您不需要列表,只需要最后两个项目。 Here is some pseudocode, I leave translating it to your language to you. 这是一些伪代码,我留给您翻译成您的语言。

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
}

Next, you can observe that the numbers are always in sequence: 接下来,您可以观察到数字始终是按顺序排列的:

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

and optimize further, if you need to 并进一步优化,如果您需要

You can get rid of the List altogether. 您可以完全摆脱列表。 Just keep the last two fibonacci numbers in two variables and calculate the next one in a loop (similar to your first loop). 只需将最后两个斐波那契数保留在两个变量中,然后在一个循环中计算下一个(类似于您的第一个循环)。

Then keep a running total of all numbers that match the condition (ie the are even and less than million) in the same loop . 然后,在同一循环中将符合条件的所有数字(即,偶数且小于百万)的运行总数保持总计。

First of all, before attempting to rewrite any code, it's useful to have a unit test. 首先,在尝试重写任何代码之前,进行单元测试非常有用。 Even the most obvious change can break something unexpected. 即使是最明显的变化也可能会破坏意想不到的东西。

Second, it's important to be very careful. 第二,要非常小心。 You'll often see in code that X calls to the same functions can be replaced by one call, assignment to variable, and use of that variable. 您通常会在代码中看到,对同一个函数的X调用可以被一个调用,分配给变量以及使用该变量代替。

However, you have to be careful doing that. 但是,您必须小心谨慎。 For instance, in your current code, you cannot really replace list.size() because the size changes all the time between calls in the same iteration. 例如,在当前代码中,您无法真正替换list.size(),因为在同一迭代中,两次调用之间的大小一直在变化。

What mostly makes your code difficult to understand is that you refer to list indices while you're updating the lists. 大多数使您的代码难以理解的是,在更新列表时引用了列表索引。 You need to save indices to variables, split into multiple lines, and perhaps add some comments to make it more readable. 您需要将索引保存到变量中,分成多行,并可能添加一些注释以使其更具可读性。

I would drop the list. 我会删除列表。 You're doing two iterations here when all you really need to do is iterate over the sequence and keep a tally variable as you go. 当您真正需要做的就是遍历序列并在运行时保持一个计数变量时,您将在此处进行两次迭代。

  • So, iterate over the Fibonacci sequence using some sort of for/while loop. 因此,使用某种for / while循环遍历斐波那契数列。
  • Add it to the tally variable if it is even. 如果它是偶数,则将其添加到tally变量中。

To do a simple iteration over the sequence, you only need to record the two most recent values ( f(n) and f(n-1) ) and (depending on your language) a temporary variable for when you update those values. 要对序列进行简单的迭代,您只需要记录两个最新值( f(n)f(n-1) )以及(取决于您的语言)更新这些值时的临时变量。 Usually something along the lines of: 通常情况如下:

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

The simples approach is a basic for loop. 简单方法是for循环的基本方法。 Or you could roll your own class that implements the Iterator<E> interface if you wanted to be all OO about it. 或者,如果您想成为所有面向对象的对象,则可以滚动自己的类来实现Iterator<E>接口。 Then your code could be as simple as: 然后您的代码可以很简单:

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

That reminds me, I must get back into Euler again some day. 这使我想起,有一天我必须再次回到欧拉。

Everything looks prettier in Python! 一切在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

Note: This was more a programming exercise than anything else. 注意:这比其他任何事情都要多。 I realise it's probably not practical to move off to Python. 我意识到转而使用Python可能不切实际。

Edit, here's the more memory efficient, no-list approach: 编辑,这是内存效率更高的无列表方法:

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

My advice would be this (I'm not going to provide an answer as it's always best to come to the conclusions yourself) 我的建议是这样(我不会提供答案,因为最好总是自己得出结论)

Try to think of why you need to store the values of the sequence? 试着想一想为什么需要存储序列的值? Your program is going to store 4000000 integers in a big array, is there really a need for this? 您的程序将要在一个大数组中存储4000000个整数,是否真的需要这样做? You could just use 2 items and zip through (using the Fibonacci calculation) adding the even numbers to a Total variable. 您可以只使用2个项目,然后通过使用Fibonacci计算进行压缩(将Fibonacci计算)添加到Total变量中。

You would do a great deal of simplifying if you don't safe all the values in a list. 如果不保护列表中的所有值,则将进行很多简化。 You could check if they are even "at the run" 您可以检查它们是否甚至在“运行”中

for example like that: 例如这样的:

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;

Well, for one thing, a member of the Fibonacci sequence is generated by the two previous values before it. 好吧,一方面,斐波那契数列的成员是由之前的两个先前值生成的。 Why not just keep two numbers in the buffer, and test if the number is even. 为什么不只在缓冲区中保留两个数字,然后测试数字是否为偶数。 If it is even, add it to your sum and stop when you reach four million. 如果是偶数,则将其加到您的总和中,直到达到四百万时停止。

code: 码:

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

Like @John Kugelman's solution, but more efficient. 类似于@John Kugelman的解决方案,但效率更高。 This will loop only 1/3 of the times and each loop is shorter with less branches and no even test required. 这只会循环1/3次,每个循环更短,分支更少,甚至不需要测试。 This takes advantage of the fact that every third fib value is even and just calculates the values in between to reset the a and b value. 这利用了以下事实:每个第三个fib值都是偶数,并且仅计算介于两者之间的值以重置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;
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM