Last active
August 29, 2015 14:13
-
-
Save robpatrick/c84bc00a99858c5b7d24 to your computer and use it in GitHub Desktop.
Roman numeral generator - just for fun.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.spartacus; | |
/** | |
* Interface that defines a method that calculates Roman numerals from a given number. | |
* | |
* @author robert.patrick | |
*/ | |
public interface RomanNumeralGenerator { | |
String generateNumber( int number ); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.spartacus | |
/** | |
* Class that calculates Roman numerals from a given number. | |
* | |
* @author robert.patrick | |
*/ | |
class RomanNumeralGeneratorImpl implements RomanNumeralGenerator { | |
private static final int MIN = 1 | |
private static final int MAX = 3999 | |
private static final int UNITS = 0 | |
private static final int TENS = 1 | |
private static final int HUNDREDS = 2 | |
private static final int THOUSANDS = 3 | |
private static final Map<Integer, Map> NUMERALS_BY_POSITION = [0:[LOW:'I', MEDIUM:'V', HIGH:'X'], | |
1:[LOW:'X', MEDIUM:'L', HIGH:'C'], | |
2:[LOW:'C', MEDIUM:'D', HIGH:'M']] | |
/** | |
* This method accepts a number between 1 and 3999 and calculates the equivalent | |
* Roman numeral. The the number is outside the acceptable boundaries then an | |
* {@code IllegalArgumentException} will be thrown. | |
* | |
* @param number The number to be converted into a Roman numeral. | |
* @return a String representing a Roman numeral. | |
*/ | |
String generateNumber( int number ) { | |
checkInputParameter( number ) | |
Map<Integer, Integer> digitsByPosition = splitNumberIntoDigitsByPosition( number ) | |
String result = '' | |
digitsByPosition.each { position, value -> | |
if ( position == THOUSANDS ) { | |
result = "${( 'M' * value )}${result}" | |
} | |
else { | |
result = "${calculateResult( value, position )}${result}" | |
} | |
} | |
result | |
} | |
/** | |
* Guard that checks the incoming value and if it's outside the acceptable limits | |
* throws an {@link IllegalArgumentException}. | |
* | |
* @param number the input variable to check. | |
* @throws when the input parameter is outside the acceptable limits. | |
*/ | |
private void checkInputParameter( int number ) throws IllegalArgumentException { | |
if ( number < MIN || number > MAX ) { | |
throw new IllegalArgumentException( "This method only supports integers between ${MIN} and ${MAX}" ) | |
} | |
} | |
/** | |
* Splits the given number into its individual digits and sticks then in a Map | |
* keyed on position. | |
* | |
* @param number the number to be converted. | |
* @return a Map of positions to digits. | |
*/ | |
private Map splitNumberIntoDigitsByPosition( int number ) { | |
Map<Integer, Integer> digitsByPosition = [:] | |
"${number}".reverse().toCharArray().eachWithIndex { char digit, Integer position -> | |
digitsByPosition[position] = Character.getNumericValue( digit ) | |
} | |
digitsByPosition | |
} | |
/** | |
* Calculate the Roman numerals based on the position of the digit in the supplied | |
* number. | |
* | |
* @param value the current digit in the number. | |
* @param position the position of the digit in the number. | |
* @return the calculated Roman numeral. | |
*/ | |
private String calculateResult( final Integer value, final Integer position ) { | |
// Get the appropriate numerals for the current position | |
Map numerals = NUMERALS_BY_POSITION[position] | |
String result = '' | |
switch( value ) { | |
case 4: | |
result = numerals.LOW + numerals.MEDIUM | |
break | |
case 5: | |
result = numerals.MEDIUM + result | |
break | |
case 9: | |
result = numerals.LOW + numerals.HIGH | |
break | |
default: | |
result = ( numerals.LOW * value ) | |
} | |
result | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.spartacus | |
import spock.lang.Unroll | |
import spock.lang.Specification | |
/** | |
* Unit test class to test {@link RomanNumeralGeneratorImpl}. | |
* | |
* @author robert.patrick | |
*/ | |
class RomanNumeralGeneratorSpec extends Specification { | |
RomanNumeralGenerator generator | |
void setup( ) { | |
generator = new RomanNumeralGeneratorImpl() | |
} | |
@Unroll( 'Called generateNumber with number #input, expecting #expected' ) | |
void "Test RomanNumeralGenerator"() { | |
when: | |
String result = generator.generateNumber( input ) | |
then: | |
result == expected | |
where: | |
input | expected | |
1 | 'I' | |
4 | 'IV' | |
5 | 'V' | |
9 | 'IX' | |
10 | 'X' | |
20 | 'XX' | |
40 | 'XL' | |
50 | 'L' | |
90 | 'XC' | |
100 | 'C' | |
150 | 'CL' | |
400 | 'CD' | |
444 | 'CDXLIV' | |
500 | 'D' | |
555 | 'DLV' | |
900 | 'CM' | |
999 | 'CMXCIX' | |
1000 | 'M' | |
1444 | 'MCDXLIV' | |
2555 | 'MMDLV' | |
2994 | 'MMCMXCIV' | |
3999 | 'MMMCMXCIX' | |
} | |
@Unroll( 'Called splitNumberIntoDigitsByPosition with number #input, expecting #expected' ) | |
void "Test splitNumberIntoDigitsByPosition sets units, tens, hundereds and thousands successfully"() { | |
when: | |
Map<Integer, Integer> result = generator.splitNumberIntoDigitsByPosition( input ) | |
then: | |
result == expected | |
where: | |
input | expected | |
1 | [0:1] | |
12 | [0:2, 1:1] | |
123 | [0:3, 1:2, 2:1] | |
1234 | [0:4, 1:3, 2:2, 3:1] | |
} | |
void "Test a number less than 1"( ) { | |
when: | |
generator.generateNumber( input ) | |
then: | |
IllegalArgumentException exception = thrown() | |
exception.message == "This method only supports integers between 1 and 3999" | |
where: | |
input << ( 0..-100 ) | |
} | |
void "Test a number greater than 3999"( ) { | |
when: | |
generator.generateNumber( input ) | |
then: | |
IllegalArgumentException exception = thrown() | |
exception.message == "This method only supports integers between 1 and 3999" | |
where: | |
input << ( 4000..4100 ) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment