diff --git a/src/main/java/com/ordestiny/tdd/kata/StringCalculator.java b/src/main/java/com/ordestiny/tdd/kata/StringCalculator.java new file mode 100755 index 0000000..3bab77e --- /dev/null +++ b/src/main/java/com/ordestiny/tdd/kata/StringCalculator.java @@ -0,0 +1,92 @@ +package com.ordestiny.tdd.kata; + +import java.text.DecimalFormat; +import java.util.Arrays; +import java.util.regex.Pattern; + +public class StringCalculator { + + private static final String NEWLINE = "\n"; + private static final String CUSTOM_DELIMITER_SUFFIX = NEWLINE; + private static final String DEFAULT_DELIMITERS = ",|" + NEWLINE; + private static final String CUSTOM_DELIMITER_PREFIX = "//"; + + public String add(String number) { + String delimiter = DEFAULT_DELIMITERS; + String numbers = number; + + if (number.startsWith(CUSTOM_DELIMITER_PREFIX)) { + delimiter = getCustomDelimiter(number); + numbers = getNumbersInput(number); + validateInput(numbers, delimiter); + } + + if (numbers.isEmpty()) { + return "0"; + } + + if (isInvalidLastCharacterIn(numbers)) { + throw new IllegalArgumentException("Number expected but found EOF"); + } + + double result = getSum(numbers, delimiter); + return format(result); + } + + private String getCustomDelimiter(String input) { + int indexOfDelimiterStart = input.lastIndexOf(CUSTOM_DELIMITER_PREFIX) + 2; + int indexOfNumbersStart = input.indexOf(CUSTOM_DELIMITER_SUFFIX); + + String unquotedDelimiter = input.substring(indexOfDelimiterStart, indexOfNumbersStart); + + return unquotedDelimiter; + } + + private String getNumbersInput(String input) { + return input.substring(input.lastIndexOf(NEWLINE) + 1); + } + + private boolean isInvalidLastCharacterIn(String numbers) { + return Character.digit(numbers.charAt(numbers.length() - 1), 10) < 0; + } + + private void validateInput(String input, String delimiter) { + for (int i = 1; i < input.length(); i += 2) { + int expectedDelimiterIndex = i + delimiter.length(); + + if (expectedDelimiterIndex < input.length()) { + String delimiterInString = input.substring(i, expectedDelimiterIndex); + + if (!delimiterInString.equals(delimiter)) { + String exceptionMessage = String.format("'%s' expected but '%s', found at position %d", delimiter, + delimiterInString, i); + throw new IllegalArgumentException(exceptionMessage); + } + } + } + } + + private double getSum(String numbers, String delimiter) { + if (delimiter.matches("[^A-Za-z0-9]")) { + // Quote any delimiter that is a special character, such as `|` + delimiter = Pattern.quote(delimiter); + } + + String[] arrayOfStringNumbers = numbers.split(delimiter); + + return sum(arrayOfStringNumbers); + } + + private double sum(String[] numbers) { + Double sum = Arrays.stream(numbers) + .map(number-> Double.valueOf(number)) + .reduce(0.0, Double::sum); + + return sum; + } + + private String format(double value) { + DecimalFormat format = new DecimalFormat("0.#"); + return format.format(value); + } +} diff --git a/src/test/java/com/ordestiny/tdd/kata/StringCalculatorTest.java b/src/test/java/com/ordestiny/tdd/kata/StringCalculatorTest.java new file mode 100755 index 0000000..0e36e35 --- /dev/null +++ b/src/test/java/com/ordestiny/tdd/kata/StringCalculatorTest.java @@ -0,0 +1,41 @@ +package com.ordestiny.tdd.kata; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; + +public class StringCalculatorTest { + + private static StringCalculator fixture; + + @BeforeAll + public static void setup() { + fixture = new StringCalculator(); + } + + @ParameterizedTest + @CsvSource({ "'',0", "1,1", "'1.1,2.2',3.3", "'1\n2,3', 6", "'//;\n1;2',3", "'//|\n1|2|3', 6", "'//sep\n2sep3',5", + "'//;\n',0" }) + public void add_WithValidNumber_SumOfNumbers(String input, String expected) { + final String actual = fixture.add(input); + + assertEquals(expected, actual); + } + + @ParameterizedTest + @ValueSource(strings = { "//|\n1|2,3", "//a\n1a2a3a4b5", "//%\n1%4$5", "//c\n1c2c" }) + public void add_WithInvalidInput_ThrowException(String input) { + Exception exception = assertThrows(IllegalArgumentException.class, () -> { + fixture.add(input); + }); + + String expectedMessage = "expected but"; + String actualMessage = exception.getMessage(); + assertTrue(actualMessage.contains(expectedMessage)); + } +}