Skip to content

Commit 3dc52a4

Browse files
committed
try to solve cyclomatic complexity issue
1 parent 6cc134a commit 3dc52a4

File tree

2 files changed

+216
-135
lines changed

2 files changed

+216
-135
lines changed

src/main/java/com/thealgorithms/conversions/WordsToNumber.java

+179-126
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.util.ArrayList;
66
import java.util.HashMap;
77
import java.util.List;
8+
import java.util.Map;
89

910
/**
1011
A Java-based utility for converting English word representations of numbers
@@ -16,11 +17,12 @@
1617
*/
1718

1819
public final class WordsToNumber {
20+
1921
private WordsToNumber() {
2022
}
2123

22-
private static final HashMap<String, Integer> NUMBER_MAP = new HashMap<>();
23-
private static final HashMap<String, BigDecimal> POWERS_OF_TEN = new HashMap<>();
24+
private static final Map<String, Integer> NUMBER_MAP = new HashMap<>();
25+
private static final Map<String, BigDecimal> POWERS_OF_TEN = new HashMap<>();
2426

2527
static {
2628
NUMBER_MAP.put("zero", 0);
@@ -60,9 +62,21 @@ private WordsToNumber() {
6062

6163
public static String convert(String numberInWords) {
6264
if (numberInWords == null) {
63-
return "Null Input";
65+
throw new WordsToNumberException(WordsToNumberException.ErrorType.NULL_INPUT, "");
6466
}
6567

68+
ArrayDeque<String> wordDeque = preprocessWords(numberInWords);
69+
BigDecimal completeNumber = convertWordQueueToBigDecimal(wordDeque);
70+
71+
return completeNumber.toString();
72+
}
73+
74+
public static BigDecimal convertToBigDecimal(String numberInWords) {
75+
String conversionResult = convert(numberInWords);
76+
return new BigDecimal(conversionResult);
77+
}
78+
79+
private static ArrayDeque<String> preprocessWords(String numberInWords) {
6680
String[] wordSplitArray = numberInWords.trim().split("[ ,-]");
6781
ArrayDeque<String> wordDeque = new ArrayDeque<>();
6882
for (String word : wordSplitArray) {
@@ -71,52 +85,115 @@ public static String convert(String numberInWords) {
7185
}
7286
wordDeque.add(word.toLowerCase());
7387
}
88+
if (wordDeque.isEmpty()) {
89+
throw new WordsToNumberException(WordsToNumberException.ErrorType.NULL_INPUT, "");
90+
}
91+
return wordDeque;
92+
}
7493

75-
List<BigDecimal> chunks = new ArrayList<>();
94+
private static void handleConjunction(boolean prevNumWasHundred, boolean prevNumWasPowerOfTen, ArrayDeque<String> wordDeque) {
95+
if (wordDeque.isEmpty()) {
96+
throw new WordsToNumberException(WordsToNumberException.ErrorType.INVALID_CONJUNCTION, "");
97+
}
98+
99+
String nextWord = wordDeque.pollFirst();
100+
String afterNextWord = wordDeque.peekFirst();
101+
102+
wordDeque.addFirst(nextWord);
103+
104+
Integer number = NUMBER_MAP.getOrDefault(nextWord, null);
105+
106+
boolean isPrevWordValid = prevNumWasHundred || prevNumWasPowerOfTen;
107+
boolean isNextWordValid = number != null && (number >= 10 || afterNextWord == null || "point".equals(afterNextWord));
108+
109+
if (!isPrevWordValid || !isNextWordValid) {
110+
throw new WordsToNumberException(WordsToNumberException.ErrorType.INVALID_CONJUNCTION, "");
111+
}
112+
}
113+
114+
private static BigDecimal handleHundred(BigDecimal currentChunk, String word, boolean prevNumWasPowerOfTen) {
115+
boolean currentChunkIsZero = currentChunk.compareTo(BigDecimal.ZERO) == 0;
116+
if (currentChunk.compareTo(BigDecimal.TEN) >= 0 || prevNumWasPowerOfTen) {
117+
throw new WordsToNumberException(WordsToNumberException.ErrorType.UNEXPECTED_WORD, word);
118+
}
119+
if (currentChunkIsZero) {
120+
currentChunk = currentChunk.add(BigDecimal.ONE);
121+
}
122+
return currentChunk.multiply(BigDecimal.valueOf(100));
123+
}
124+
125+
private static void handlePowerOfTen(List<BigDecimal> chunks, BigDecimal currentChunk, BigDecimal powerOfTen, String word, boolean prevNumWasPowerOfTen) {
126+
boolean currentChunkIsZero = currentChunk.compareTo(BigDecimal.ZERO) == 0;
127+
if (currentChunkIsZero || prevNumWasPowerOfTen) {
128+
throw new WordsToNumberException(WordsToNumberException.ErrorType.UNEXPECTED_WORD, word);
129+
}
130+
BigDecimal nextChunk = currentChunk.multiply(powerOfTen);
131+
132+
if (!(chunks.isEmpty() || isAdditionSafe(chunks.getLast(), nextChunk))) {
133+
throw new WordsToNumberException(WordsToNumberException.ErrorType.UNEXPECTED_WORD, word);
134+
}
135+
chunks.add(nextChunk);
136+
}
137+
138+
private static BigDecimal handleNumber(List<BigDecimal> chunks, BigDecimal currentChunk, String word, Integer number) {
139+
boolean currentChunkIsZero = currentChunk.compareTo(BigDecimal.ZERO) == 0;
140+
if (number == 0 && !(currentChunkIsZero && chunks.isEmpty())) {
141+
throw new WordsToNumberException(WordsToNumberException.ErrorType.UNEXPECTED_WORD, word);
142+
}
143+
BigDecimal bigDecimalNumber = BigDecimal.valueOf(number);
144+
145+
if (!currentChunkIsZero && !isAdditionSafe(currentChunk, bigDecimalNumber)) {
146+
throw new WordsToNumberException(WordsToNumberException.ErrorType.UNEXPECTED_WORD, word);
147+
}
148+
return currentChunk.add(bigDecimalNumber);
149+
}
150+
151+
private static void handlePoint(List<BigDecimal> chunks, BigDecimal currentChunk, ArrayDeque<String> wordDeque) {
152+
boolean currentChunkIsZero = currentChunk.compareTo(BigDecimal.ZERO) == 0;
153+
if (!currentChunkIsZero) {
154+
chunks.add(currentChunk);
155+
}
156+
157+
String decimalPart = convertDecimalPart(wordDeque);
158+
chunks.add(new BigDecimal(decimalPart));
159+
}
160+
161+
private static void handleNegative(boolean isNegative) {
162+
if (isNegative) {
163+
throw new WordsToNumberException(WordsToNumberException.ErrorType.MULTIPLE_NEGATIVES, "");
164+
}
165+
throw new WordsToNumberException(WordsToNumberException.ErrorType.INVALID_NEGATIVE, "");
166+
}
167+
168+
private static BigDecimal convertWordQueueToBigDecimal(ArrayDeque<String> wordDeque) {
76169
BigDecimal currentChunk = BigDecimal.ZERO;
170+
List<BigDecimal> chunks = new ArrayList<>();
171+
172+
boolean isNegative = "negative".equals(wordDeque.peek());
173+
if (isNegative) wordDeque.poll();
77174

78-
boolean isNegative = false;
79175
boolean prevNumWasHundred = false;
80176
boolean prevNumWasPowerOfTen = false;
81177

82-
String errorMessage = null;
83-
84-
while (!wordDeque.isEmpty() && errorMessage == null) {
178+
while (!wordDeque.isEmpty()) {
85179
String word = wordDeque.poll();
86-
boolean currentChunkIsZero = currentChunk.compareTo(BigDecimal.ZERO) == 0;
87180

88-
boolean isConjunction = word.equals("and");
89-
if (isConjunction && isValidConjunction(prevNumWasHundred, prevNumWasPowerOfTen, wordDeque)) {
90-
continue;
91-
}
92-
93-
if (word.equals("hundred")) {
94-
if (currentChunk.compareTo(BigDecimal.TEN) >= 0 || prevNumWasPowerOfTen) {
95-
errorMessage = "Invalid Input. Unexpected Word: " + word;
181+
switch (word) {
182+
case "and" -> {
183+
handleConjunction(prevNumWasHundred, prevNumWasPowerOfTen, wordDeque);
96184
continue;
97185
}
98-
if (currentChunkIsZero) {
99-
currentChunk = currentChunk.add(BigDecimal.ONE);
186+
case "hundred" -> {
187+
currentChunk = handleHundred(currentChunk, word, prevNumWasPowerOfTen);
188+
prevNumWasHundred = true;
189+
continue;
100190
}
101-
currentChunk = currentChunk.multiply(BigDecimal.valueOf(100));
102-
prevNumWasHundred = true;
103-
continue;
104191
}
105192
prevNumWasHundred = false;
106193

107194
BigDecimal powerOfTen = POWERS_OF_TEN.getOrDefault(word, null);
108195
if (powerOfTen != null) {
109-
if (currentChunkIsZero || prevNumWasPowerOfTen) {
110-
errorMessage = "Invalid Input. Unexpected Word: " + word;
111-
continue;
112-
}
113-
BigDecimal nextChunk = currentChunk.multiply(powerOfTen);
114-
115-
if (!(chunks.isEmpty() || isAdditionSafe(chunks.getLast(), nextChunk))) {
116-
errorMessage = "Invalid Input. Unexpected Word: " + word;
117-
continue;
118-
}
119-
chunks.add(nextChunk);
196+
handlePowerOfTen(chunks, currentChunk, powerOfTen, word, prevNumWasPowerOfTen);
120197
currentChunk = BigDecimal.ZERO;
121198
prevNumWasPowerOfTen = true;
122199
continue;
@@ -125,120 +202,96 @@ public static String convert(String numberInWords) {
125202

126203
Integer number = NUMBER_MAP.getOrDefault(word, null);
127204
if (number != null) {
128-
if (number == 0 && !(currentChunkIsZero && chunks.isEmpty())) {
129-
errorMessage = "Invalid Input. Unexpected Word: " + word;
130-
continue;
131-
}
132-
BigDecimal bigDecimalNumber = BigDecimal.valueOf(number);
133-
134-
if (currentChunkIsZero || isAdditionSafe(currentChunk, bigDecimalNumber)) {
135-
currentChunk = currentChunk.add(bigDecimalNumber);
136-
} else {
137-
errorMessage = "Invalid Input. Unexpected Word: " + word;
138-
}
205+
currentChunk = handleNumber(chunks, currentChunk, word, number);
139206
continue;
140207
}
141208

142-
if (word.equals("point")) {
143-
if (!currentChunkIsZero) {
144-
chunks.add(currentChunk);
145-
}
146-
currentChunk = BigDecimal.ZERO;
147-
148-
String decimalPart = convertDecimalPart(wordDeque);
149-
if (!decimalPart.startsWith("I")) {
150-
chunks.add(new BigDecimal(decimalPart));
151-
} else {
152-
errorMessage = decimalPart;
153-
}
154-
continue;
155-
}
156-
157-
if (word.equals("negative")) {
158-
if (isNegative) {
159-
errorMessage = "Invalid Input. Multiple 'Negative's detected.";
160-
} else {
161-
isNegative = chunks.isEmpty() && currentChunkIsZero;
209+
switch (word) {
210+
case "point" -> {
211+
handlePoint(chunks, currentChunk, wordDeque);
212+
currentChunk = BigDecimal.ZERO;
213+
continue;
162214
}
163-
continue;
215+
case "negative" -> handleNegative(isNegative);
164216
}
165217

166-
errorMessage = "Invalid Input. " + (isConjunction ? "Unexpected 'and' placement" : "Unknown Word: " + word);
218+
throw new WordsToNumberException(WordsToNumberException.ErrorType.UNKNOWN_WORD, word);
167219
}
168220

169-
if (errorMessage != null) {
170-
return errorMessage;
171-
}
172-
173-
if (!(currentChunk.compareTo(BigDecimal.ZERO) == 0)) {
221+
if (currentChunk.compareTo(BigDecimal.ZERO) != 0) {
174222
chunks.add(currentChunk);
175223
}
176-
BigDecimal completeNumber = combineChunks(chunks);
177224

178-
return isNegative ? completeNumber.multiply(BigDecimal.valueOf(-1)).toString() : completeNumber.toString();
179-
}
180-
181-
private static boolean isValidConjunction(boolean prevNumWasHundred, boolean prevNumWasPowerOfTen, ArrayDeque<String> wordDeque) {
182-
if (wordDeque.isEmpty()) {
183-
return false;
184-
}
225+
BigDecimal completeNumber = combineChunks(chunks);
226+
return isNegative ? completeNumber.multiply(BigDecimal.valueOf(-1)) :
227+
completeNumber;
228+
}
185229

186-
String nextWord = wordDeque.pollFirst();
187-
String afterNextWord = wordDeque.peekFirst();
230+
private static boolean isAdditionSafe(BigDecimal currentChunk, BigDecimal number) {
231+
int chunkDigitCount = currentChunk.toString().length();
232+
int numberDigitCount = number.toString().length();
233+
return chunkDigitCount > numberDigitCount;
234+
}
188235

189-
wordDeque.addFirst(nextWord);
236+
private static String convertDecimalPart(ArrayDeque<String> wordDeque) {
237+
StringBuilder decimalPart = new StringBuilder(".");
238+
239+
while (!wordDeque.isEmpty()) {
240+
String word = wordDeque.poll();
241+
Integer number = NUMBER_MAP.getOrDefault(word, null);
242+
if (number == null) {
243+
throw new WordsToNumberException(WordsToNumberException.ErrorType.UNEXPECTED_WORD_AFTER_POINT, word);
244+
}
245+
decimalPart.append(number);
246+
}
247+
248+
boolean missingNumbers = decimalPart.length() == 1;
249+
if (missingNumbers) {
250+
throw new WordsToNumberException(WordsToNumberException.ErrorType.MISSING_DECIMAL_NUMBERS, "");
251+
}
252+
return decimalPart.toString();
253+
}
190254

191-
Integer number = NUMBER_MAP.getOrDefault(nextWord, null);
255+
private static BigDecimal combineChunks(List<BigDecimal> chunks) {
256+
BigDecimal completeNumber = BigDecimal.ZERO;
257+
for (BigDecimal chunk : chunks) {
258+
completeNumber = completeNumber.add(chunk);
259+
}
260+
return completeNumber;
261+
}
262+
}
192263

193-
boolean isPrevWordValid = prevNumWasHundred || prevNumWasPowerOfTen;
194-
boolean isNextWordValid = number != null && (number >= 10 || afterNextWord == null || afterNextWord.equals("point"));
264+
class WordsToNumberException extends RuntimeException {
195265

196-
return isPrevWordValid && isNextWordValid;
197-
}
266+
enum ErrorType {
267+
NULL_INPUT("'null' or empty input provided"),
268+
UNKNOWN_WORD("Unknown Word: "),
269+
UNEXPECTED_WORD("Unexpected Word: "),
270+
UNEXPECTED_WORD_AFTER_POINT("Unexpected Word (after Point): "),
271+
MISSING_DECIMAL_NUMBERS("Decimal part is missing numbers."),
272+
MULTIPLE_NEGATIVES("Multiple 'Negative's detected."),
273+
INVALID_NEGATIVE("Incorrect 'negative' placement"),
274+
INVALID_CONJUNCTION("Incorrect 'and' placement");
198275

199-
private static boolean isAdditionSafe(BigDecimal currentChunk, BigDecimal number) {
200-
int chunkDigitCount = currentChunk.toString().length();
201-
int numberDigitCount = number.toString().length();
202-
return chunkDigitCount > numberDigitCount;
203-
}
276+
private final String message;
204277

205-
private static String convertDecimalPart(ArrayDeque<String> wordDeque) {
206-
StringBuilder decimalPart = new StringBuilder(".");
207-
String errorMessage = null;
278+
ErrorType(String message) {
279+
this.message = message;
280+
}
208281

209-
while (!wordDeque.isEmpty()) {
210-
String word = wordDeque.poll();
211-
Integer number = NUMBER_MAP.getOrDefault(word, null);
212-
if (number == null) {
213-
errorMessage = "Invalid Input. Unexpected Word (after Point): " + word;
214-
break;
282+
public String formatMessage(String details) {
283+
return "Invalid Input. " + message + (details.isEmpty() ? "" : details);
284+
}
215285
}
216-
decimalPart.append(number);
217-
}
218286

219-
if (errorMessage != null) {
220-
return errorMessage;
221-
}
287+
public final ErrorType errorType;
222288

223-
if (decimalPart.length() == 1) {
224-
return "Invalid Input. Decimal Part is missing Numbers.";
225-
}
226-
return decimalPart.toString();
227-
}
228-
229-
private static BigDecimal combineChunks(List<BigDecimal> chunks) {
230-
BigDecimal completeNumber = BigDecimal.ZERO;
231-
for (BigDecimal chunk : chunks) {
232-
completeNumber = completeNumber.add(chunk);
233-
}
234-
return completeNumber;
235-
}
289+
WordsToNumberException(ErrorType errorType, String details) {
290+
super(errorType.formatMessage(details));
291+
this.errorType = errorType;
292+
}
236293

237-
public static BigDecimal convertToBigDecimal(String numberInWords) {
238-
String conversionResult = convert(numberInWords);
239-
if (conversionResult.startsWith("I")) {
240-
return null;
294+
public ErrorType getErrorType() {
295+
return errorType;
296+
}
241297
}
242-
return new BigDecimal(conversionResult);
243-
}
244-
}

0 commit comments

Comments
 (0)