我无聊又疲倦于更严肃的项目...。直到在YouTube上看到这段Computerphile视频。现在,我再次受到启发。现在我想花一些时间在一个小的控制台程序中实现该递归算法。幸运的是,在这种情况下,递归速度非常慢。因此,我最终写了一个小类,该类实现了循环算法以查找任何\ $ F_n \ $。

我只是在学习C#,所以我最关心的是最佳实践和风格。我想打下良好的基础,所以欢迎大家提供反馈。

特殊问题


我的缩进正确吗?
我是否给变量起了可怕的名字? (我认为它们闻起来有点香。)
哪种实施更好?我感觉这是一个或一半的打z声中的六个。
我应该担心碰到ulong类型的上限吗?斐波那契数增长很快。有没有更好的表示非常大的数字?
Implementation2

class Fibonacci
{
    private int _n;
    private ulong _value;
    private ulong _FnMinus1;
    private ulong _FnMinus2;

    public int n 
    {
        get { return _n; }
        set { _n = value; } 
    }

    public ulong Value 
    { 
        get {return _value;}
        //set { _value = value; }
    }

    public void Calculate() 
    { 
        if (n <= 0)
        {
            throw new ArgumentOutOfRangeException("n","Can't calculate Fn when n is less than or equal to zero.");
        }
        else if (n == 1 || n == 2)
        {
            _value = 1;
        }
        else
        {
            //initialize previous results
            _FnMinus1 = 1;
            _FnMinus2 = 1;

            for (int i = 0; i <= (n - 2) ; i++)
            {
                _value = _FnMinus1 + _FnMinus2;
                _FnMinus2 = _FnMinus1;
                _FnMinus1 = _value;
            }
        }
    }

    //constructors
    public Fibonacci(int n)
    {
        _n = n;
        Calculate();
    }

    public Fibonacci() { }

}


更新:在此处关注问题。

评论

在使用时检查此Fibonacci实现:)

对于大整数,确实可以使用BigInteger类。

有趣的@AlexiLevenkov。我不知道它的表现如何。

必须在“最佳问题标题”类别中提名该人!

@ Vogel612当然,整数(就是整数)溢出:System.OverflowException:算术运算导致溢出。只需在实现1中的for循环中更改限制,您将得到一个异常;除非它在不受检查的环境中运行,否则它会幸福而无声地超载,并且给出错误的答案。

#1 楼

缩进和格式化以及算法本身都很好。

您的命名不一致,请比较nValue_value_FnMinus1。有多种约定可以遵循,但是一致性是最重要的。

_FnMinus1_FnMinus2仅在一种方法中使用,因此可以使该方法局部化。该API有点奇怪:那么看来int result = fib.Calculate(i)n并不是必需的。

此外,现在没有任何东西可以使Valuen保持同步。该类应保持不变,即Value始终是Fibanacci的Value数。您可以在n分配中完成工作,或者在n更改时将Value标记为脏。无论哪种方式,您基本上都可以摆脱n,所以这是另一种方法。我更喜欢摆脱Calculaten,我只是提到这是为了让您认为保留它们很有意义。 1的好处是仅进行一次分配。由于该类是(即将成为)无状态的,因此可以完全重用。另一方面,2在循环内部声明Value,这最小化了它的范围(很好)。

评论


\ $ \ begingroup \ $
是的。我打破了黄金法则。我先实现了功能,然后才真正需要它们。谢谢。
\ $ \ endgroup \ $
–RubberDuck
14年6月19日在23:53

\ $ \ begingroup \ $
如果要使用无状态的类,则最好将其设为静态。
\ $ \ endgroup \ $
– svick
2014年6月20日在2:02

\ $ \ begingroup \ $
@svick我同意这种特殊情况。通常,使类/函数静态化可以防止DI或其他类型的多态性,因此我认为新手不应强调这条规则。
\ $ \ endgroup \ $
–皮埃尔·梅纳德(Pierre Menard)
2014年6月20日15:24

#2 楼

Microsoft的.NET样式指南建议不要在标识符中使用下划线或任何匈牙利符号。

您很正确,对于足够大的n值,将发生溢出。通过使用System.Numerics.BigInteger可以避免溢出。但是,这给您的程序增加了很多混乱,并且比简单的教育项目可能要乏味。

我认为您已经错过了有关面向对象编程的重要要点,这就是对象应该使自己保持自洽状态。您已经将Calculate()公开为公共方法,并且希望调用者在获取Calculate()之前先调用.Value。这对呼叫者是不公平的负担,他们可能会忽略这样做。相反,更好的设计是让.Value吸气剂在必要时自动重新计算。 (Calculate()方法应该是私有的。)如果自上次调用n以来.Value并未减少,则将继续执行以前的计算(而不是从头开始)的更智能设计。

评论


\ $ \ begingroup \ $
我得到了下划线,但是我没有使用任何匈牙利符号。 Fn是数学符号。我仍然不喜欢那些名字。我确实需要更好的东西。
\ $ \ endgroup \ $
–RubberDuck
2014年6月20日在2:38

\ $ \ begingroup \ $
您的问题不包含匈牙利语。如果您有使用前缀的想法,例如mValue而不是_value,我会优先提到匈牙利表示法。
\ $ \ endgroup \ $
– 200_success
2014年6月20日在2:41

\ $ \ begingroup \ $
啊。好。谢谢。我显然很困惑。
\ $ \ endgroup \ $
–RubberDuck
2014年6月20日在2:41

\ $ \ begingroup \ $
谨慎推荐匈牙利人。不习惯的做法是在变量名中嵌入类型名,而最初的匈牙利语(实际上是有用的)就是在名称中嵌入逻辑含义。 zh.wikipedia.org/wiki/匈牙利语
\ $ \ endgroup \ $
–科斯
2014年6月20日14:23

\ $ \ begingroup \ $
@Kos根据该链接,匈牙利表示法最初被认为是用于减少类型的语言的解决方案。实际上,我唯一一次推荐的是VBScript。匈牙利符号应该用火燃烧。
\ $ \ endgroup \ $
–RubberDuck
2014年6月20日在22:20



#3 楼

类设计

某些类设计是做一些对他人影响最小的事情。将值传递给构造函数并从值中读取答案,或者在显式设置计数时,可以进行计算在功能上都没有错;他们都工作;但更符合预期的形状将是

public class Maths {
    public long CalculateFibbonacci(int count){
      // return the calculated value
    }
}


,这样可以使用
或者可以使它成为静态类中的静态函数(尽管,由于它们阻碍了单元测试,我倾向于避免使用它们)。设计好的类(增加了使用类成员的额外步骤-n,值)的痕迹这是一种OO语言,因此应该使用一类。

注意:

两者都没有功能上的问题,如果使用得当,

class Program {
    static void Main(string[] args) {
        var fib = new Fibbonacci();
        for (var i = 1; i <= 50; i++) {
           Console.WriteLine(fib.Calculate(i));
        }
        Console.WriteLine("Press enter to close...");
        Console.ReadLine();
    }
}    


用法有很多相关步骤,以致在使用中很容易出错,我真的不建议

编码样式

压痕,间距,括号位置等。可以被认为是引起无休止争吵的宗教问题。在代码库中使用单一样式是有价值的,因为它使读取/查看变得更简单。当两个团队成员在其编辑器中设置不同的样式并且每次更改时都保持更改整个文件的间距,括号位置时,这也很乏味,但是没有一个唯一的样式已经开发出许多不同的样式,并且是最受欢迎的主流所有这些都有其优缺点。

因此,如果一个人正在与其他人一起工作并且多数/共识风格并不完全可笑,则应该吸纳它并使用共同的风格。

话虽如此,当前的代码样式看起来不错。它清晰易读。

班级的公共成员应该具有Pascal Cased的描述性。 n对于公共成员来说是个坏名字。它不会告诉用户其含义或用法。

击中限制



如果要检查编码样式和设计是编程工作,那么除非要进行大量处理是您要解决的设计问题之一,否则,我不担心结果太大。

如果它在现实中有某种用法,请检查此用法,以查看它是否可能溢出以及是否溢出,然后再担心。除非有明确的需求,否则担心它可以视为过早优化的一种形式

评论


\ $ \ begingroup \ $
稍​​后我将在一些困惑中使用它,因此需要关注大量数字。我同意n是一个坏名字,但这不是匈牙利符号。这是数学符号。 F sub n。我愿意提出更好的建议。
\ $ \ endgroup \ $
–RubberDuck
14年6月20日在11:09

\ $ \ begingroup \ $
我实际上在想匈牙利语的_FnMinus1和_FnMinus2,但看起来好像我不在了-它们只是缩写。我的错。我将更新答案。至于n:没有更好的名字,这确实暗示它不太适合成员。如果我们将其取出并使用长版calculate(int n)的函数版本,则n作为该函数的参数名称将位于“人们期望的那种东西”下
\ $ \ endgroup \ $
– AlanT
14年6月20日,11:55

\ $ \ begingroup \ $
我认为我有一个不错的。顺序位置,但我需要考虑一下API的工作方式。我想知道是否有必要将N暴露于斐波那契之外的任何事物。
\ $ \ endgroup \ $
–RubberDuck
14年6月20日在11:59

\ $ \ begingroup \ $
正如我所说,将API分成太多步骤是可疑的,这里不需要它。除了{创建,设置OrdinalPostion,调用计算,读取结果}之外,我们还可以{Create,Invoke计算传递顺序位置和读取结果}这样,无需在班级
\ $ \ endgroup \ $
– AlanT
14年6月20日在12:03

\ $ \ begingroup \ $
我解决了该类希望其客户端保持同步的问题。我现在正在考虑是否隐藏OrdinalPosition和Value,或者仅实现公共Calculate()函数。谢谢您的意见。一旦确定了处理方式,我可能会发布后续报告。
\ $ \ endgroup \ $
–RubberDuck
2014年6月20日15:28



#4 楼

给出了大多数问题的答案,除了在命名和使用BigInteger方面的一致性之外,没有更多要对这些问题说些什么,但是更笼统的评论是,斐波那契序列构成了一个奇数类。它不只是一个类,而且是一个函数,而您实现的基本上是一种使该函数的参数更严格的方法,并且倾向于某种奇怪的类设计。不是因为您的设计很奇怪,而是因为该工具不是完成任务的最佳工具。如果将函数建模为类,结果将流向何处?是作为计算所得的属性还是单个方法的返回值?

因此,无需建模返回第n个数字的函数,而是可以创建一个表示斐波那契数列的类

class FibonacciSequence : IEnumerable<BigInteger>
{

    IEnumerator<BigInteger> IEnumerable<BigInteger>.GetEnumerator()
    {
        BigInteger prev1 = 1;
        BigInteger prev2 = 1;
        yield return 1;
        yield return 1;
        while (true)
        {
            var next = prev1 + prev2;
            yield return next;
            prev1 = prev2;
            prev2 = next;
        }
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return ((IEnumerable<BigInteger>)this).GetEnumerator();
    }
}


然后可以像这样使用它

new FibonacciSequence().Take(2).Last();


以获得序列中的第二个数字。当然,您可以使用备忘录和索引器来加快连续调用。下面是这样做的(不是线程安全的)

class FibonacciSequence : IEnumerable<BigInteger>
{
    List<BigInteger> list = new List<BigInteger> { 1, 1 };

    IEnumerator<BigInteger> IEnumerable<BigInteger>.GetEnumerator()
    {
        return list.Concat(iterate()).GetEnumerator();   
    }

    IEnumerable<BigInteger> iterate()
    {
        BigInteger prev1;
        BigInteger prev2;
        prev1 = list[list.Count - 2];
        prev2 = list[list.Count - 1];
        while (true)
        {
            var next = prev1 + prev2;
            list.Add(next);             
            yield return next;
            prev1 = prev2;
            prev2 = next;

        }
    }

    public BigInteger this[int index]
    {
        get
        {
            if (index >= list.Count)
            {
                list.AddRange(iterate().Take(index - list.Count + 1));
            }
            return list[index];
        }

    }
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return ((IEnumerable<BigInteger>)this).GetEnumerator();
    }
}


评论


\ $ \ begingroup \ $
那绝对是很棒的建议。谢谢。
\ $ \ endgroup \ $
–RubberDuck
2014年6月24日13:30