From a69fbfe25957421265e088b6a20da88895369530 Mon Sep 17 00:00:00 2001 From: James Walker Date: Wed, 7 Nov 2018 15:02:44 -0500 Subject: [PATCH] ruby - alphametics --- ruby/alphametics/README.md | 58 +++++++++++++++++++ ruby/alphametics/alphametics.rb | 12 ++++ ruby/alphametics/alphametics_test.rb | 86 ++++++++++++++++++++++++++++ 3 files changed, 156 insertions(+) create mode 100644 ruby/alphametics/README.md create mode 100644 ruby/alphametics/alphametics.rb create mode 100644 ruby/alphametics/alphametics_test.rb diff --git a/ruby/alphametics/README.md b/ruby/alphametics/README.md new file mode 100644 index 0000000..468c66a --- /dev/null +++ b/ruby/alphametics/README.md @@ -0,0 +1,58 @@ +# Alphametics + +Write a function to solve alphametics puzzles. + +[Alphametics](https://en.wikipedia.org/wiki/Alphametics) is a puzzle where +letters in words are replaced with numbers. + +For example `SEND + MORE = MONEY`: + +```text + S E N D + M O R E + +----------- +M O N E Y +``` + +Replacing these with valid numbers gives: + +```text + 9 5 6 7 + 1 0 8 5 + +----------- +1 0 6 5 2 +``` + +This is correct because every letter is replaced by a different number and the +words, translated into numbers, then make a valid sum. + +Each letter must represent a different digit, and the leading digit of +a multi-digit number must not be zero. + +Write a function to solve alphametics puzzles. + +* * * * + +For installation and learning resources, refer to the +[exercism help page](http://exercism.io/languages/ruby). + +For running the tests provided, you will need the Minitest gem. Open a +terminal window and run the following command to install minitest: + + gem install minitest + +If you would like color output, you can `require 'minitest/pride'` in +the test file, or note the alternative instruction, below, for running +the test file. + +Run the tests from the exercise directory using the following command: + + ruby alphametics_test.rb + +To include color from the command line: + + ruby -r minitest/pride alphametics_test.rb + + +## Submitting Incomplete Solutions +It's possible to submit an incomplete solution so you can see how others have completed the exercise. diff --git a/ruby/alphametics/alphametics.rb b/ruby/alphametics/alphametics.rb new file mode 100644 index 0000000..889d150 --- /dev/null +++ b/ruby/alphametics/alphametics.rb @@ -0,0 +1,12 @@ +# Alphametics class +class Alphametics + def self.solve(puzzle) + chars = puzzle.chars & ('A'..'Z').to_a + (0..9).to_a.permutation(chars.length).each do |numbers| + equation = puzzle.tr(chars.join, numbers.join) + next if equation =~ /\b0/ + return Hash[chars.zip(numbers)] if eval(equation) + end + {} + end +end diff --git a/ruby/alphametics/alphametics_test.rb b/ruby/alphametics/alphametics_test.rb new file mode 100644 index 0000000..7f7da0b --- /dev/null +++ b/ruby/alphametics/alphametics_test.rb @@ -0,0 +1,86 @@ +require 'minitest/autorun' +require_relative 'alphametics' + +# Common test data version: 1.2.0 a86a774 +class AlphameticsTest < Minitest::Test + def test_puzzle_with_three_letters + # skip + puzzle = 'I + BB == ILL' + expected = { 'B' => 9, 'I' => 1, 'L' => 0 } + assert_equal expected, Alphametics.solve(puzzle) + end + + def test_solution_must_have_unique_value_for_each_letter + # skip + puzzle = 'A == B' + expected = {} + assert_equal expected, Alphametics.solve(puzzle) + end + + def test_leading_zero_solution_is_invalid + # skip + puzzle = 'ACA + DD == BD' + expected = {} + assert_equal expected, Alphametics.solve(puzzle) + end + + def test_puzzle_with_four_letters + # skip + puzzle = 'AS + A == MOM' + expected = { 'A' => 9, 'M' => 1, 'O' => 0, 'S' => 2 } + assert_equal expected, Alphametics.solve(puzzle) + end + + def test_puzzle_with_six_letters + # skip + puzzle = 'NO + NO + TOO == LATE' + expected = { 'A' => 0, 'E' => 2, 'L' => 1, 'N' => 7, + 'O' => 4, 'T' => 9 } + assert_equal expected, Alphametics.solve(puzzle) + end + + def test_puzzle_with_seven_letters + # skip + puzzle = 'HE + SEES + THE == LIGHT' + expected = { 'E' => 4, 'G' => 2, 'H' => 5, 'I' => 0, + 'L' => 1, 'S' => 9, 'T' => 7 } + assert_equal expected, Alphametics.solve(puzzle) + end + + # The obvious algorithm can take a long time to solve this puzzle, + # but an optimised solution can solve it fairly quickly. + # (It's OK to submit your solution without getting this test to pass.) + def test_puzzle_with_eight_letters + # skip + puzzle = 'SEND + MORE == MONEY' + expected = { 'D' => 7, 'E' => 5, 'M' => 1, 'N' => 6, + 'O' => 0, 'R' => 8, 'S' => 9, 'Y' => 2 } + assert_equal expected, Alphametics.solve(puzzle) + end + + # The obvious algorithm can take a long time to solve this puzzle, + # but an optimised solution can solve it fairly quickly. + # (It's OK to submit your solution without getting this test to pass.) + def test_puzzle_with_ten_letters + # skip + puzzle = 'AND + A + STRONG + OFFENSE + AS + A + GOOD == DEFENSE' + expected = { 'A' => 5, 'D' => 3, 'E' => 4, 'F' => 7, + 'G' => 8, 'N' => 0, 'O' => 2, 'R' => 1, + 'S' => 6, 'T' => 9 } + assert_equal expected, Alphametics.solve(puzzle) + end + + # The obvious algorithm can take a long time to solve this puzzle, + # but an optimised solution can solve it fairly quickly. + # (It's OK to submit your solution without getting this test to pass.) + # + # Remove 'do_not_' from the test name to run this test. + def do_not_test_puzzle_with_ten_letters_and_199_addends + # skip + puzzle = 'THIS + A + FIRE + THEREFORE + FOR + ALL + HISTORIES + I + TELL + A + TALE + THAT + FALSIFIES + ITS + TITLE + TIS + A + LIE + THE + TALE + OF + THE + LAST + FIRE + HORSES + LATE + AFTER + THE + FIRST + FATHERS + FORESEE + THE + HORRORS + THE + LAST + FREE + TROLL + TERRIFIES + THE + HORSES + OF + FIRE + THE + TROLL + RESTS + AT + THE + HOLE + OF + LOSSES + IT + IS + THERE + THAT + SHE + STORES + ROLES + OF + LEATHERS + AFTER + SHE + SATISFIES + HER + HATE + OFF + THOSE + FEARS + A + TASTE + RISES + AS + SHE + HEARS + THE + LEAST + FAR + HORSE + THOSE + FAST + HORSES + THAT + FIRST + HEAR + THE + TROLL + FLEE + OFF + TO + THE + FOREST + THE + HORSES + THAT + ALERTS + RAISE + THE + STARES + OF + THE + OTHERS + AS + THE + TROLL + ASSAILS + AT + THE + TOTAL + SHIFT + HER + TEETH + TEAR + HOOF + OFF + TORSO + AS + THE + LAST + HORSE + FORFEITS + ITS + LIFE + THE + FIRST + FATHERS + HEAR + OF + THE + HORRORS + THEIR + FEARS + THAT + THE + FIRES + FOR + THEIR + FEASTS + ARREST + AS + THE + FIRST + FATHERS + RESETTLE + THE + LAST + OF + THE + FIRE + HORSES + THE + LAST + TROLL + HARASSES + THE + FOREST + HEART + FREE + AT + LAST + OF + THE + LAST + TROLL + ALL + OFFER + THEIR + FIRE + HEAT + TO + THE + ASSISTERS + FAR + OFF + THE + TROLL + FASTS + ITS + LIFE + SHORTER + AS + STARS + RISE + THE + HORSES + REST + SAFE + AFTER + ALL + SHARE + HOT + FISH + AS + THEIR + AFFILIATES + TAILOR + A + ROOFS + FOR + THEIR + SAFE == FORTRESSES' + expected = { 'A' => 1, 'E' => 0, 'F' => 5, 'H' => 8, + 'I' => 7, 'L' => 2, 'O' => 6, 'R' => 3, + 'S' => 4, 'T' => 9 } + assert_equal expected, Alphametics.solve(puzzle) + end +end