Skip to content

Commit aa1792b

Browse files
committed
Merge branch 'issue-31' into develop
2 parents 9e737b1 + d545e76 commit aa1792b

File tree

10 files changed

+1113
-126
lines changed

10 files changed

+1113
-126
lines changed

README.md

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,9 @@ stable version.
4242

4343
## Advanced Usage
4444

45-
The `Romans` package uses a Lexer-Parser approach to convert Roman number to
46-
Integer, using a Grammar Token library.
45+
The `Romans` package uses a Lexer-Parser approach and a Deterministic Finite
46+
Automaton (DFA) to convert Roman number to Integer, using a Grammar Token
47+
library.
4748

4849
```php
4950
use Romans\Grammar\Grammar;
@@ -58,10 +59,13 @@ $tokens = $lexer->tokenize('MCMXCIX');
5859

5960
/*
6061
$tokens = [
61-
0 => 'M', // Grammar::T_M
62-
1 => 'CM', // Grammar::T_CM
63-
2 => 'XC', // Grammar::T_XC
64-
3 => 'IX', // Grammar::T_IX
62+
0 => 'M' // Grammar::T_M
63+
1 => 'C', // Grammar::T_C
64+
2 => 'M', // Grammar::T_M
65+
3 => 'X', // Grammar::T_X
66+
4 => 'C', // Grammar::T_C
67+
5 => 'I', // Grammar::T_I
68+
6 => 'X', // Grammar::T_X
6569
];
6670
*/
6771

@@ -125,10 +129,42 @@ $filter = new IntToRoman();
125129
$result = $filter->filter(0); // N
126130
```
127131

132+
## Techniques
133+
134+
This section describes some techniques this package uses to convert Roman
135+
numbers into integer and vice-versa.
136+
137+
### Deterministic Finite Automaton (DFA)
138+
139+
A DFA was developed to check if a string with Roman number is valid. This
140+
technique was choiced because some implementations simply convert the `$input`
141+
without checking some rules, like four chars sequentially.
142+
143+
The current automaton definition is declared below.
144+
145+
```plain
146+
M = (Q, Σ, δ, q0, F)
147+
Q = { a, b, c, d, e, f, g, y, z }
148+
Σ = { I, V, X, L, C, D, M, N }
149+
q0 = g
150+
F = { z }
151+
152+
z -> ε
153+
y -> $z
154+
a -> y | Iy | IIy | IIIy
155+
b -> a | IVy | Va | IXy
156+
c -> b | Xb | XXb | XXXb
157+
d -> c | XLb | Lc | XCb
158+
e -> d | Cd | CCd | CCCd
159+
f -> e | CDd | De | CMd
160+
g -> f | Nz | Mg
161+
```
162+
128163
## References
129164

130165
* Rapid Tables: [How to Convert Roman Numerals to Numbers](http://www.rapidtables.com/convert/number/how-roman-numerals-to-number.htm)
131166
* Wikipedia: [Zero in Roman Numerals](https://en.wikipedia.org/wiki/Roman_numerals#Zero)
167+
* Wikipedia: [Deterministic Finite Automaton](https://en.wikipedia.org/wiki/Deterministic_finite_automaton)
132168

133169
## License
134170

src/Filter/IntToRoman.php

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,20 +39,25 @@ public function filter(int $value) : string
3939
}
4040

4141
$tokens = $this->getGrammar()->getTokens();
42-
$values = array_reverse($this->getGrammar()->getValues());
42+
$values = array_reverse($this->getGrammar()->getValuesWithModifiers(), true /* preserve keys */);
43+
$result = '';
4344

4445
if ($value === 0) {
45-
$token = array_search(0, $values);
46+
$dataset = $values[0];
4647

47-
return $tokens[$token];
48-
}
48+
foreach ($dataset as $token) {
49+
$result = $result . $tokens[$token];
50+
}
4951

50-
$result = '';
52+
return $result;
53+
}
5154

52-
foreach ($values as $token => $current) {
55+
foreach ($values as $current => $dataset) {
5356
while ($current > 0 && $value >= $current) {
54-
$value = $value - $current;
55-
$result = $result . $tokens[$token];
57+
$value = $value - $current;
58+
foreach ($dataset as $token) {
59+
$result = $result . $tokens[$token];
60+
}
5661
}
5762
}
5863

src/Grammar/Grammar.php

Lines changed: 59 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,14 @@
77
*/
88
class Grammar
99
{
10-
const T_N = 'N';
11-
const T_I = 'I';
12-
const T_IV = 'IV';
13-
const T_V = 'V';
14-
const T_IX = 'IX';
15-
const T_X = 'X';
16-
const T_XL = 'XL';
17-
const T_L = 'L';
18-
const T_XC = 'XC';
19-
const T_C = 'C';
20-
const T_CD = 'CD';
21-
const T_D = 'D';
22-
const T_CM = 'CM';
23-
const T_M = 'M';
10+
const T_N = 'N';
11+
const T_I = 'I';
12+
const T_V = 'V';
13+
const T_X = 'X';
14+
const T_L = 'L';
15+
const T_C = 'C';
16+
const T_D = 'D';
17+
const T_M = 'M';
2418

2519
/**
2620
* Get Tokens
@@ -30,20 +24,14 @@ class Grammar
3024
public function getTokens() : array
3125
{
3226
return [
33-
'T_N' => self::T_N,
34-
'T_I' => self::T_I,
35-
'T_IV' => self::T_IV,
36-
'T_V' => self::T_V,
37-
'T_IX' => self::T_IX,
38-
'T_X' => self::T_X,
39-
'T_XL' => self::T_XL,
40-
'T_L' => self::T_L,
41-
'T_XC' => self::T_XC,
42-
'T_C' => self::T_C,
43-
'T_CD' => self::T_CD,
44-
'T_D' => self::T_D,
45-
'T_CM' => self::T_CM,
46-
'T_M' => self::T_M,
27+
'T_N' => self::T_N,
28+
'T_I' => self::T_I,
29+
'T_V' => self::T_V,
30+
'T_X' => self::T_X,
31+
'T_L' => self::T_L,
32+
'T_C' => self::T_C,
33+
'T_D' => self::T_D,
34+
'T_M' => self::T_M,
4735
];
4836
}
4937

@@ -55,20 +43,49 @@ public function getTokens() : array
5543
public function getValues()
5644
{
5745
return [
58-
'T_N' => 0,
59-
'T_I' => 1,
60-
'T_IV' => 4,
61-
'T_V' => 5,
62-
'T_IX' => 9,
63-
'T_X' => 10,
64-
'T_XL' => 40,
65-
'T_L' => 50,
66-
'T_XC' => 90,
67-
'T_C' => 100,
68-
'T_CD' => 400,
69-
'T_D' => 500,
70-
'T_CM' => 900,
71-
'T_M' => 1000,
46+
'T_N' => 0,
47+
'T_I' => 1,
48+
'T_V' => 5,
49+
'T_X' => 10,
50+
'T_L' => 50,
51+
'T_C' => 100,
52+
'T_D' => 500,
53+
'T_M' => 1000,
7254
];
7355
}
56+
57+
/**
58+
* Get Modifiers
59+
*
60+
* @return array Modifiers Available
61+
*/
62+
public function getModifiers()
63+
{
64+
return [
65+
4 => ['T_I', 'T_V'],
66+
9 => ['T_I', 'T_X'],
67+
40 => ['T_X', 'T_L'],
68+
90 => ['T_X', 'T_C'],
69+
400 => ['T_C', 'T_D'],
70+
900 => ['T_C', 'T_M'],
71+
];
72+
}
73+
74+
/**
75+
* Get Values with Modifiers
76+
*
77+
* @return array Values with Modifiers Available
78+
*/
79+
public function getValuesWithModifiers()
80+
{
81+
$values = array_map(function ($value) {
82+
return [$value];
83+
}, array_flip($this->getValues()));
84+
85+
$valuesWithModifiers = $values + $this->getModifiers(); // merge and keep keys (append)
86+
87+
ksort($valuesWithModifiers);
88+
89+
return $valuesWithModifiers;
90+
}
7491
}

src/Lexer/Lexer.php

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,15 +57,6 @@ public function tokenize(string $content) : array
5757
throw $exception;
5858
}
5959

60-
// Lookahead
61-
if ($position + 1 < $length) {
62-
$next = $content[$position + 1];
63-
if (isset($numerals[$current . $next])) {
64-
$current = $current . $next;
65-
$position = $position + 1;
66-
}
67-
}
68-
6960
$result[] = $current;
7061
$position = $position + 1;
7162
}

0 commit comments

Comments
 (0)