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";
}
}
运行测试:
然后我们推进到 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;
}
}
再次测试:
按照相同的思路,我们可以推进到下一个数字 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;
}
}
运行测试:
如果按照我们当前的思路,当数字越来越大时,我们只需按照对应规则增加一段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;
}
}
再次测试:
上面其实是一种通用的优化代码的技巧,当你的代码有太多的条件判断如 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'
];
.
.
}
然后运行测试: