【问题标题】:How to clean up my code如何清理我的代码
【发布时间】:2010-06-14 13:52:48
【问题描述】:

作为新手,我真的在努力学习如何让代码尽可能简单,同时完成应有的工作。

我做的问题来自Project Euler,上面写着

斐波那契数列中的每个新项都是通过添加 前两期。从 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;
}

【问题讨论】:

    标签: java refactoring


    【解决方案1】:

    其他响应者都给出了很好的答案。我想向你展示重构是如何在行动中发挥作用的,不仅仅是为了了解有关斐波那契数的特定问题,而是作为一个迭代过程,将代码仔细削减到最低限度。重构让我们从工作但复杂的代码开始,然后一步一步地稳步削减它。让我向您展示在朝着最终解决方案努力的过程中可以采取的所有中间步骤。

    注意:我已将您的初始起始值更改为 1 和 1 而不是 1 和 2。严格来说,斐波那契数列以两个 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;如果可以的话。这很好地展示了如何生成更清晰的代码。是的,@Mark Peters,颠倒列表可能效率低下 - 但看看最后发生了什么!它走了。在变得更好的过程中让你的代码变得更糟是可以的。这一切都在跟进中。
    • 现在这就是我所说的性感代码! (我需要多出去吗?!)我认为我真的需要练习重构我的代码的过程,这是一个很棒的教程。我想将此标记为正确,但另一个有更多选票。我希望这不会被反对?
    • @Mark - 好点。我添加了一个切换到链接列表的步骤。这不是绝对必要的,因为最终列表会被完全删除,但无论如何,这里的重点是过程而不是最终结果。
    【解决方案2】:

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

    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 [...]
    

    如果需要,还可以进一步优化

    【讨论】:

    • 答案绝对没有写清楚...永远不要使用只有一个字符长的变量...
    • 像 for 循环中经常使用的“i”吗?那么最好叫它“loop_index_increment_counter”。
    • @Janusz:我不想看到你处理数组的代码。如果for (int i = 0; i &lt; ... 不是自我记录和全球标准的习语,我不知道是什么。
    • i 有时会为 i 做一个例外,但有时你可以使用一些东西来显示你正在计数的东西,比如 currentShop。有了一个好的 ide,你就可以完成代码,阅读它的人尤其是在列出的循环中会更容易。
    【解决方案3】:

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

    然后在同一个循环中保持所有符合条件的数字的总和(即偶数且小于百万)

    【讨论】:

      【解决方案4】:

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

      其次,要非常小心,这一点很重要。您经常会在代码中看到,对相同函数的 X 调用可以被一次调用、分配给变量和使用该变量替换。

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

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

      【讨论】:

      • 反对票反对。我不知道为什么它被否决(两次!)。即使是单元测试,虽然对学习者来说很可怕,但也是一个好主意,斐波那契生成器是其中的主要候选者。
      【解决方案5】:

      我会删除列表。当您真正需要做的只是遍历序列并随时保留一个计数变量时,您在这里进行了两次迭代。

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

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

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

      simples 方法是一个基本的 for 循环。或者,如果您想完全了解它,您可以推出自己的实现Iterator&lt;E&gt; 接口的类。那么你的代码可以很简单:

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

      这提醒了我,总有一天我必须重新回到欧拉。

      【讨论】:

        【解决方案6】:

        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
        

        【讨论】:

          【解决方案7】:

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

          试着想想为什么需要存储序列的值?你的程序将在一个大数组中存储 4000000 个整数,真的需要这个吗?您可以只使用 2 个项目并通过(使用斐波那契计算)将偶数添加到 Total 变量中。

          【讨论】:

          • 数组大小只有 34 左右
          • 重点是存储所有成员不是必需的,因为斐波那契数列的性质。
          • 抱歉,我误读了您的代码 - 但 futureelite7 是正确的 - 我的意思是建议不要将结果存储在数组中。想象一下,如果您想生成 100 万个斐波那契数,并考虑在您的程序中会占用多少内存!
          【解决方案8】:

          嗯,一方面,斐波那契数列的一个成员是由它之前的两个先前值生成的。为什么不在缓冲区中保留两个数字,并测试该数字是否为偶数。如果是偶数,请将其添加到您的总和中,并在达到 400 万时停止。

          代码:

          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】:

            如果您不保护列表中的所有值,您会做大量的简化工作。您可以检查他们是否甚至“在运行”

            例如这样:

            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;
            

            【讨论】:

              【解决方案10】:

              类似于@John Kugelman 的解决方案,但效率更高。这将只循环 1/3 的时间,每个循环更短,分支更少,甚至不需要测试。这利用了每三个 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;
                  }
              }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2011-06-20
                • 2021-04-27
                • 2021-02-05
                • 1970-01-01
                • 1970-01-01
                • 2021-05-20
                • 1970-01-01
                相关资源
                最近更新 更多