lots of exercises in java... from https://github.com/exercism/java

Alphametics.java 4.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. import java.util.ArrayList;
  2. import java.util.Arrays;
  3. import java.util.Collections;
  4. import java.util.LinkedHashMap;
  5. import java.util.LinkedHashSet;
  6. import java.util.List;
  7. import java.util.Map;
  8. import java.util.Optional;
  9. import java.util.Set;
  10. import java.util.stream.Collectors;
  11. public class Alphametics {
  12. private final List<String> wordsToSum;
  13. private final String wordResult;
  14. Alphametics(String userInput) {
  15. String[] questionAndAnswer = userInput.split("==");
  16. wordsToSum = Arrays.stream(questionAndAnswer[0].split("\\+"))
  17. .map(String::new)
  18. .map(String::trim)
  19. .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
  20. wordResult = questionAndAnswer[1].trim();
  21. }
  22. Map<Character, Integer> solve() throws UnsolvablePuzzleException {
  23. AlphameticsRecursion solver = new AlphameticsRecursion(getDistinctCharacters());
  24. solver.generate();
  25. return solver.get().orElseThrow(UnsolvablePuzzleException::new);
  26. }
  27. /**
  28. * Returns the list of distinct characters in this alphametic puzzle.
  29. */
  30. private List<Character> getDistinctCharacters() {
  31. Set<Character> distinctCharacters = new LinkedHashSet<>();
  32. wordsToSum.forEach(word -> distinctCharacters.addAll(toChar(word)));
  33. distinctCharacters.addAll(toChar(wordResult));
  34. return new ArrayList<>(distinctCharacters);
  35. }
  36. private List<Character> toChar(String toChar) {
  37. return toChar.chars()
  38. .mapToObj(c -> (char) c)
  39. .collect(Collectors.toList());
  40. }
  41. private class AlphameticsRecursion {
  42. private final List<Character> characters;
  43. private LinkedHashMap<Character, Integer> validPermutation;
  44. private AlphameticsRecursion(List<Character> characters) {
  45. this.characters = characters;
  46. }
  47. private void generate() {
  48. generate(new LinkedHashMap<>(), 0, new boolean[10]);
  49. }
  50. private void generate(LinkedHashMap<Character, Integer> permutation, int index, boolean[] isDigitsUsed) {
  51. // base case
  52. if (index == characters.size()) {
  53. if (!isLeadingDigitZero(permutation) && isSumTally(permutation)) {
  54. validPermutation = new LinkedHashMap<>(permutation);
  55. }
  56. return;
  57. }
  58. for (int i = 0; i <= 9; i++) { // loop through digits 0 to 9
  59. if (isDigitsUsed[i]) {
  60. continue;
  61. }
  62. permutation.put(characters.get(index), i);
  63. isDigitsUsed[i] = true;
  64. generate(permutation, index + 1, isDigitsUsed);
  65. isDigitsUsed[i] = false;
  66. }
  67. }
  68. private Optional<LinkedHashMap<Character, Integer>> get() {
  69. return Optional.ofNullable(validPermutation);
  70. }
  71. /**
  72. * Returns true if the mapping letters to digits using {@code letterToDigit} will result in having zero as a
  73. * leading digit.
  74. */
  75. private boolean isLeadingDigitZero(Map<Character, Integer> letterToDigit) {
  76. return letterToDigit.keySet().stream()
  77. .filter(key -> letterToDigit.get(key) == 0) // Find the character that is mapped to digit 0
  78. .filter(charMappedToZero -> wordResult.charAt(0) == charMappedToZero // If the first character in
  79. // wordResult is mapped to 0
  80. || wordsToSum.stream() // If the first character in any of wordsToSum is mapped to 0
  81. .map(word -> word.charAt(0))
  82. .anyMatch(character -> character == charMappedToZero))
  83. .count() == 1; // One letter maps to zero and is a leading character
  84. }
  85. /**
  86. * Returns true if the {@code letterToDigit} solves the alphametic puzzle.
  87. */
  88. private boolean isSumTally(Map<Character, Integer> letterToDigit) {
  89. long actualSum = wordsToSum.stream()
  90. .mapToLong(word -> mapToNumber(letterToDigit, word))
  91. .sum();
  92. long expectedSum = mapToNumber(letterToDigit, wordResult);
  93. return actualSum == expectedSum;
  94. }
  95. /**
  96. * Returns the long value of {@code word}, mapped using {@code letterToDigit}.
  97. */
  98. private long mapToNumber(Map<Character, Integer> letterToDigit, String word) {
  99. StringBuilder builder = new StringBuilder();
  100. for (int i = 0; i < word.length(); i++) {
  101. int digit = letterToDigit.get(word.charAt(i));
  102. builder.append(digit);
  103. }
  104. return Long.parseLong(builder.toString());
  105. }
  106. }
  107. }