From 8db22c126c27923971549bf866e58df92d22441b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=B8rre=20A=2E=20Opedal=20Lunde?= Date: Sat, 27 Jan 2024 21:21:54 +0100 Subject: [PATCH] LeetCode problem: 12. Integer to Roman --- .../java/com/bl/integer_to_roman/README.md | 58 +++++ .../com/bl/integer_to_roman/Solution.java | 59 +++++ .../com/bl/integer_to_roman/SolutionTest.java | 202 ++++++++++++++++++ 3 files changed, 319 insertions(+) create mode 100644 src/main/java/com/bl/integer_to_roman/README.md create mode 100644 src/main/java/com/bl/integer_to_roman/Solution.java create mode 100644 src/test/java/com/bl/integer_to_roman/SolutionTest.java diff --git a/src/main/java/com/bl/integer_to_roman/README.md b/src/main/java/com/bl/integer_to_roman/README.md new file mode 100644 index 0000000..d124b48 --- /dev/null +++ b/src/main/java/com/bl/integer_to_roman/README.md @@ -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` \ No newline at end of file diff --git a/src/main/java/com/bl/integer_to_roman/Solution.java b/src/main/java/com/bl/integer_to_roman/Solution.java new file mode 100644 index 0000000..53cfeb2 --- /dev/null +++ b/src/main/java/com/bl/integer_to_roman/Solution.java @@ -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(); + } +} diff --git a/src/test/java/com/bl/integer_to_roman/SolutionTest.java b/src/test/java/com/bl/integer_to_roman/SolutionTest.java new file mode 100644 index 0000000..5dd7ed7 --- /dev/null +++ b/src/test/java/com/bl/integer_to_roman/SolutionTest.java @@ -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 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 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 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); + } +} \ No newline at end of file