2.罗马数字

未匹配的标注
  • 本系列文章为laracasts.com 的系列视频教程——Code Katas in PHP 的学习笔记。若喜欢该系列视频,可去该网站订阅后下载该系列视频,支持正版
  • Kata 是一个简短,可重复的编程挑战,可以帮助我们进行快速地编程练习。
  • 开发模型仍旧是 TDD(测试驱动开发),视频中使用的是 phpspec 进行开发,笔记中使用了 Laravel 应用,因此代码有不同。

本节说明

  • 对应第 2 小节:Roman Numerals

本节内容

本节我们来做罗马数字转换的 Kata。跟据罗马数字的规则,对于 1,应该返回 I;对于 2,返回 II;对于 5,返回 V。以下是一份参考:

数值 罗马数字
1 I
2 II
4 IV
5 V
9 IX
10 X
40 XL
50 L
90 XC
100 C
400 CD
500 D
900 DM
1000 M

我们只进行 50 以下的数字转换,其他的规则是类似的。首先我们新建测试:

$ php artisan make:test RomanNumeralsConverterTest --unit

tests\Unit\RomanNumeralsConverterTest.php

<?php

namespace Tests\Unit;

use Tests\TestCase;
use App\RomanNumeralsConverter;

class RomanNumeralsConverterTest extends TestCase
{
    public function setUp()
    {
        parent::setUp();

        $this->romanNumeralsConverter = new RomanNumeralsConverter();
    }

    /** @test */
    public function it_returns_I_for_1()
    {
        $this->assertEquals("I",$this->romanNumeralsConverter->convert(1));
    }
}

对于最开始的测试,我们最快速使它通过:

app\RomanNumeralsConverter.php

<?php

namespace App;

class RomanNumeralsConverter 
{
    public function convert()
    {
        return "I";
    }
}

运行测试:

file
然后我们推进到 2:

    .
    .
    /** @test */
    public function it_returns_II_for_2()
    {
        $this->assertEquals("II",$this->romanNumeralsConverter->convert(2));
    }
}

现在我们需要来修改代码了:

<?php

namespace App;

class RomanNumeralsConverter 
{
    public function convert($number)
    {
        $solution = '';

        while($number >= 1)
        {
            $solution .= 'I';

            $number -= 1;
        }
        return $solution;
    }
}

再次测试:
file
按照相同的思路,我们可以推进到下一个数字 4 和 5:

    .
    .
    /** @test */
    public function it_returns_IV_for_4()
    {
        $this->assertEquals("IV",$this->romanNumeralsConverter->convert(4));
    }

    /** @test */
    public function it_returns_V_for_5()
    {
        $this->assertEquals("V",$this->romanNumeralsConverter->convert(5));
    }
}
<?php

namespace App;

class RomanNumeralsConverter 
{
    public function convert($number)
    {
        $solution = '';

        while($number >= 5)
        {
            $solution .= 'V';

            $number -= 5;
        }

        while($number >= 4)
        {
            $solution .= 'IV';

            $number -= 4;
        }

        while($number >= 1)
        {
            $solution .= 'I';

            $number -= 1;
        }

        return $solution;
    }
}

运行测试:
file
如果按照我们当前的思路,当数字越来越大时,我们只需按照对应规则增加一段while循环的代码即可。然而,这段代码在我们看来几乎是一模一样的。所以,我们到了进行重构的时候了:

<?php

namespace App;

class RomanNumeralsConverter 
{
    private static $lookup = [
        5 => 'V',
        4 => 'IV',
        1 => 'I'
    ];

    public function convert($number)
    {
        $solution = '';

        foreach(static::$lookup as $limit => $glyph)
        {
            while($number >= $limit)
            {
                $solution .= $glyph;

                $number -= $limit;
            }
        }

        return $solution;
    }
}

再次测试:
file
上面其实是一种通用的优化代码的技巧,当你的代码有太多的条件判断如 if,elseif,else等时,不妨像上面那样改为列表(数组)驱动的方式,那样会大大简化你的代码。现在我们只需编写新的测试,当测试不通过时我们补充新的对应规则,然后让测试通过即可:

    .
    .
    /** @test */
    public function it_returns_X_for_10()
    {
        $this->assertEquals("X",$this->romanNumeralsConverter->convert(10));
    }

    /** @test */
    public function it_returns_XL_for_40()
    {
        $this->assertEquals("XL",$this->romanNumeralsConverter->convert(40));
    }

    /** @test */
    public function it_returns_L_for_50()
    {
        $this->assertEquals("L",$this->romanNumeralsConverter->convert(50));
    }

    /** @test */
    public function it_returns_XLIV_for_44()
    {
        $this->assertEquals("XLIV",$this->romanNumeralsConverter->convert(44));
    }
}

添加对应规则:

<?php

namespace App;

class RomanNumeralsConverter 
{
    private static $lookup = [
        50 => 'L',
        40 => 'XL',
        10 => 'X',
        9 => 'IX',
        5 => 'V',
        4 => 'IV',
        1 => 'I'
    ];
    .
    .
}

然后运行测试:
file

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
讨论数量: 0
发起讨论 只看当前版本


暂无话题~