对此我是新手,我真的想学习如何保持代码尽可能简单,同时完成应有的工作。
我所做的问题来自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;
}
答案 0 :(得分:33)
其他响应者都给出了很好的答案。我想向您展示重构如何在行动中起作用,不仅仅是针对这个特定问题,了解有关斐波那契数字的事情,而是作为一个迭代过程,小心地将代码简化为最低限度。重构让我们从工作但复杂的代码开始,一步一步地逐步削减它。让我向您展示在完成最终解决方案的过程中您可以采取的所有步骤。
注意:我已将初始起始值更改为1和1而不是1和2.严格来说,Fibonacci序列以两个1开头,如1,1,2,3,5 ......
对于初学者来说,要摆脱重复的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;
}
我们将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;
}
下一个优化是组合两个循环。您可以在生成偶数时检查偶数,而不是先生成所有数字,然后再检查偶数。
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;
}
现在您可能会注意到,您从未引用过索引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)
我会删除列表。当你真正需要做的就是迭代序列并保持一个计数变量时,你在这里进行两次迭代。
要对序列进行简单的迭代,您只需要记录两个最近的值( 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;
}
}