Skip to content

Instantly share code, notes, and snippets.

@robpatrick
Last active August 29, 2015 14:13
Show Gist options
  • Save robpatrick/c84bc00a99858c5b7d24 to your computer and use it in GitHub Desktop.
Save robpatrick/c84bc00a99858c5b7d24 to your computer and use it in GitHub Desktop.
Roman numeral generator - just for fun.
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 );
}
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
}
}
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