Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions src/main/java/com/bl/integer_to_roman/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# 12. Integer to Roman

Difficulty: `Medium`
Topics: `Hash Table`, `Math`, `String`

Roman numerals are represented by seven different symbols: `I`, `V`, `X`, `L`, `C`, `D` and `M`.

```text
Symbol Value
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
```

For example, `2` is written as `II` in Roman numeral, just two ones added together. `12` is written as `XII`, which is
simply `X + II`. The number `27` is written as `XXVII`, which is `XX + V + II`.

Roman numerals are usually written largest to smallest from left to right. However, the numeral for four is not `IIII`.
Instead, the number four is written as `IV`. Because the one is before the five we subtract it making four. The same
principle applies to the number nine, which is written as `IX`. There are six instances where subtraction is used:

- `I` can be placed before `V` (5) and `X` (10) to make 4 and 9.
- `X` can be placed before `L` (50) and `C` (100) to make 40 and 90.
- `C` can be placed before `D` (500) and `M` (1000) to make 400 and 900.

Given an integer, convert it to a roman numeral.

**Example 1:**

```text
Input: num = 3
Output: "III"
Explanation: 3 is represented as 3 ones.
```

**Example 2:**

```text
Input: num = 58
Output: "LVIII"
Explanation: L = 50, V = 5, III = 3.
```

**Example 3:**

```text
Input: num = 1994
Output: "MCMXCIV"
Explanation: M = 1000, CM = 900, XC = 90 and IV = 4.
```

**Constraints:**

- `1 <= num <= 3999`
59 changes: 59 additions & 0 deletions src/main/java/com/bl/integer_to_roman/Solution.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.bl.integer_to_roman;

/**
* This is the solution to the LeetCode problem: 12. Integer to Roman
*
* @author Børre A. Opedal Lunde
* @since 2024.01.27
*/
public class Solution {

// This class stores the Roman numeral and its corresponding integer value.
// (First I used a map, but the solution became really clumsy.)
private static class RomanNumeral {

private final int value;
private final String numeral;

public RomanNumeral(final int number, final String symbol) {
this.value = number;
this.numeral = symbol;
}

// No getters are needed because it's a private inner class.
}

// The Roman numerals are sorted in descending order because we want to
// start with the largest and subtract our way down.
private static final RomanNumeral[] ROMAN_NUMERALS = {
new RomanNumeral(1000, "M"),
new RomanNumeral(900, "CM"),
new RomanNumeral(500, "D"),
new RomanNumeral(400, "CD"),
new RomanNumeral(100, "C"),
new RomanNumeral(90, "XC"),
new RomanNumeral(50, "L"),
new RomanNumeral(40, "XL"),
new RomanNumeral(10, "X"),
new RomanNumeral(9, "IX"),
new RomanNumeral(5, "V"),
new RomanNumeral(4, "IV"),
new RomanNumeral(1, "I")
};

public String intToRoman(int num) {

// It is faster to use a string builder than a string.
final StringBuilder builder = new StringBuilder();

// Go through every Roman numeral and subtract our way down, while
// appending the Roman numeral to the string builder.
for (final RomanNumeral romanNumeral : ROMAN_NUMERALS) {
while (num >= romanNumeral.value) {
num -= romanNumeral.value;
builder.append(romanNumeral.numeral);
}
}
return builder.toString();
}
}
202 changes: 202 additions & 0 deletions src/test/java/com/bl/integer_to_roman/SolutionTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
package com.bl.integer_to_roman;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.assertEquals;

/**
* This is the test to the LeetCode problem: 12. Integer to Roman
*
* @author Børre A. Opedal Lunde
* @since 2024.01.27
*/
@DisplayName("Integer to Roman")
class SolutionTest {

final Solution solution = new Solution();

@Test
@DisplayName("Example one")
void exampleOne() {
int num = 3;
String expected = "III";
String actual = solution.intToRoman(num);
assertEquals(expected, actual);
}

@Test
@DisplayName("Example two")
void exampleTwo() {
int num = 58;
String expected = "LVIII";
String actual = solution.intToRoman(num);
assertEquals(expected, actual);
}

@Test
@DisplayName("Example three")
void exampleThree() {
int num = 1994;
String expected = "MCMXCIV";
String actual = solution.intToRoman(num);
assertEquals(expected, actual);
}

static Stream<Arguments> providePrincipalNumbers() {
return Stream.of(
Arguments.of(1, "I"),
Arguments.of(5, "V"),
Arguments.of(10, "X"),
Arguments.of(50, "L"),
Arguments.of(100, "C"),
Arguments.of(500, "D"),
Arguments.of(1000, "M")
);
}

@ParameterizedTest(name = "Number {0} is Roman numeral {1}")
@DisplayName("Principal Roman numbers")
@MethodSource("providePrincipalNumbers")
void principalRomanNumerals(int num, String expected) {
String actual = solution.intToRoman(num);
assertEquals(expected, actual);
}

static Stream<Arguments> provideSubtractiveNumbers() {
return Stream.of(
Arguments.of(4, "IV"),
Arguments.of(9, "IX"),
Arguments.of(40, "XL"),
Arguments.of(90, "XC"),
Arguments.of(400, "CD"),
Arguments.of(900, "CM")
);
}

@ParameterizedTest(name = "Number {0} is Roman numeral {1}")
@DisplayName("Subtractive Roman numbers")
@MethodSource("provideSubtractiveNumbers")
void subtractiveRomanNumerals(int num, String expected) {
String actual = solution.intToRoman(num);
assertEquals(expected, actual);
}

static Stream<Arguments> provideNumbersFromOneToOneHundred() {
return Stream.of(
Arguments.of(1, "I"),
Arguments.of(2, "II"),
Arguments.of(3, "III"),
Arguments.of(4, "IV"),
Arguments.of(5, "V"),
Arguments.of(6, "VI"),
Arguments.of(7, "VII"),
Arguments.of(8, "VIII"),
Arguments.of(9, "IX"),
Arguments.of(10, "X"),
Arguments.of(11, "XI"),
Arguments.of(12, "XII"),
Arguments.of(13, "XIII"),
Arguments.of(14, "XIV"),
Arguments.of(15, "XV"),
Arguments.of(16, "XVI"),
Arguments.of(17, "XVII"),
Arguments.of(18, "XVIII"),
Arguments.of(19, "XIX"),
Arguments.of(20, "XX"),
Arguments.of(21, "XXI"),
Arguments.of(22, "XXII"),
Arguments.of(23, "XXIII"),
Arguments.of(24, "XXIV"),
Arguments.of(25, "XXV"),
Arguments.of(26, "XXVI"),
Arguments.of(27, "XXVII"),
Arguments.of(28, "XXVIII"),
Arguments.of(29, "XXIX"),
Arguments.of(30, "XXX"),
Arguments.of(31, "XXXI"),
Arguments.of(32, "XXXII"),
Arguments.of(33, "XXXIII"),
Arguments.of(34, "XXXIV"),
Arguments.of(35, "XXXV"),
Arguments.of(36, "XXXVI"),
Arguments.of(37, "XXXVII"),
Arguments.of(38, "XXXVIII"),
Arguments.of(39, "XXXIX"),
Arguments.of(40, "XL"),
Arguments.of(41, "XLI"),
Arguments.of(42, "XLII"),
Arguments.of(43, "XLIII"),
Arguments.of(44, "XLIV"),
Arguments.of(45, "XLV"),
Arguments.of(46, "XLVI"),
Arguments.of(47, "XLVII"),
Arguments.of(48, "XLVIII"),
Arguments.of(49, "XLIX"),
Arguments.of(50, "L"),
Arguments.of(51, "LI"),
Arguments.of(52, "LII"),
Arguments.of(53, "LIII"),
Arguments.of(54, "LIV"),
Arguments.of(55, "LV"),
Arguments.of(56, "LVI"),
Arguments.of(57, "LVII"),
Arguments.of(58, "LVIII"),
Arguments.of(59, "LIX"),
Arguments.of(60, "LX"),
Arguments.of(61, "LXI"),
Arguments.of(62, "LXII"),
Arguments.of(63, "LXIII"),
Arguments.of(64, "LXIV"),
Arguments.of(65, "LXV"),
Arguments.of(66, "LXVI"),
Arguments.of(67, "LXVII"),
Arguments.of(68, "LXVIII"),
Arguments.of(69, "LXIX"),
Arguments.of(70, "LXX"),
Arguments.of(71, "LXXI"),
Arguments.of(72, "LXXII"),
Arguments.of(73, "LXXIII"),
Arguments.of(74, "LXXIV"),
Arguments.of(75, "LXXV"),
Arguments.of(76, "LXXVI"),
Arguments.of(77, "LXXVII"),
Arguments.of(78, "LXXVIII"),
Arguments.of(79, "LXXIX"),
Arguments.of(80, "LXXX"),
Arguments.of(81, "LXXXI"),
Arguments.of(82, "LXXXII"),
Arguments.of(83, "LXXXIII"),
Arguments.of(84, "LXXXIV"),
Arguments.of(85, "LXXXV"),
Arguments.of(86, "LXXXVI"),
Arguments.of(87, "LXXXVII"),
Arguments.of(88, "LXXXVIII"),
Arguments.of(89, "LXXXIX"),
Arguments.of(90, "XC"),
Arguments.of(91, "XCI"),
Arguments.of(92, "XCII"),
Arguments.of(93, "XCIII"),
Arguments.of(94, "XCIV"),
Arguments.of(95, "XCV"),
Arguments.of(96, "XCVI"),
Arguments.of(97, "XCVII"),
Arguments.of(98, "XCVIII"),
Arguments.of(99, "XCIX"),
Arguments.of(100, "C")
);
}

@ParameterizedTest(name = "Number {0} is Roman numeral {1}")
@DisplayName("Roman numerals from one to one hundred")
@MethodSource("provideNumbersFromOneToOneHundred")
void romanNumeralsFromOneToOneHundred(int num, String expected) {
String actual = solution.intToRoman(num);
assertEquals(actual, expected);
}
}