Skip to content

Instantly share code, notes, and snippets.

@BenNG
Last active August 16, 2016 11:46
Show Gist options
  • Save BenNG/278a141e600c6e5ef08f1468f45175fc to your computer and use it in GitHub Desktop.
Save BenNG/278a141e600c6e5ef08f1468f45175fc to your computer and use it in GitHub Desktop.

Solve your sudoku puzzles with Elixir

Introduction

Why this project ?

I started learning Elixir few months ago and after finishing the Elixir channel on Exercism.io, I was looking for a bigger project. I had this Euler project in my head for a long time and the timing was excellent to start it.

Why Sudoku ?

Sudoku is maybe not the most exciting thing but everybody knows what it is and probably already tried once to solve one. So people will be more likely to understand me if I write an article or show the project for job interviews.

Get started

First things first, You need to install Elixir on your computer. Then you should see something like this:

elixir --version
Erlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:4:4] [async-threads:10] [kernel-poll:false]
Elixir 1.3.2

mix --version
Erlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:4:4] [async-threads:10] [kernel-poll:false]
Mix 1.3.2

Use mix to create and test your project

mix new sudoku
cd sudoku
mix test

Compiling 1 file (.ex)
Generated sudoku app
.

Finished in 0.04 seconds
1 test, 0 failures

Before we start, I will try to let you the choice of just reading the article or coding the project. Here you will find the source code and the tests. When all the tests are green for a module you can continue.

Part-1: Create the board and apply values

At the end of this part you will be able to generate an empty board, apply initial values on it and generate this: image of sudoku representation using Sudoku.Display.pretty

Introduction

  • There are 9 rows from 0 to 8 and this one is the first one (be careful this is 0 based)
[{0, 1}, {1, 1}, {2, 1}, {3, 1}, {4, 1}, {5, 1}, {6, 1}, {7, 1}, {8, 1}]
  • There are 9 columns from 0 to 8 and this one is the first one
[{1, 0}, {1, 1}, {1, 2}, {1, 3}, {1, 4}, {1, 5}, {1, 6}, {1, 7}, {1, 8}]
  • There are 9 boxes and this one is the number zero
[{0,0} {1,0} {2,0} {0,1} {1,1} {2,1} {0,2} {1,2} {2,2}]

Rows, columns, boxes are also called units.

We will create 2 modules Sudoku.Board and Sudoku.ApplyValues.
I will follow the Programming Elixir 1.2 book conventions to create the structure of the project.

├── _build
...
├── config
│   └── config.exs
├── index.js
├── lib
│   ├── sudoku
│   │   ├── board.ex <--
│   │   ├── data_structure_utils.ex <--
│   │   ├── strategies
│   │   │   ├── apply_values.ex <--
│   └── sudoku.ex
├── mix.exs
├── package.json
├── README.md
├── stuff.js
└── test
    ├── sudoku_board_test.exs <--
    ├── sudoku_test.exs
    └── test_helper.exs

Board

source code
test file

Generating a row:

# board.ex
defmodule Sudoku.Board do
  def generate_row(n, size \\ 9) do
    Enum.reduce((size - 1)..0, [], &( [{&1, n} | &2]) )
  end
end

Notice the default parameter and the way we define our range, by starting at the end, we don't have to Enum.reverse the list. If you want to test your work you can use iex.

iex -S mix # load the context of our project in the shell
Sudoku.Board.generate_row(4)
[{0,4},{1,4},{2,4},{3,4},{4,4},{5,4},{6,4},{7,4},{8,4}]

Cool ! Let's use our test file:

# sudoku_board_test.exs
defmodule SudokuBoardTest do
use ExUnit.Case, async: true
  test "get coordinates of row number 4 (0 based)" do
    assert Sudoku.Board.generate_row(4) ==
      [{0,4},{1,4},{2,4},{3,4},{4,4},{5,4},{6,4},{7,4},{8,4}]
  end
end

Did you notice the difference between source code file extension and test one ? *.ex files are source files and will be compiled while *.exs will not be compiled ! They are scripts.

At the root of your project invoke mix test to run your test.

Generating rows

We have to do the same from 0 to 8.

# board.ex
defmodule Sudoku.Board do
    
  ...
  def generate_rows(size \\ 9) do
    Enum.reduce((size - 1)..0, [], &([generate_row(&1, size) | &2]))
  end
end

and test it

# sudoku_board_test.exs
defmodule SudokuBoardTest do
use ExUnit.Case, async: true
    
  ...
  test "generate_rows" do
    assert Sudoku.Board.generate_rows == [
      [{0,0},{1,0},{2,0},{3,0},{4,0},{5,0},{6,0},{7,0},{8,0},],
      [{0,1},{1,1},{2,1},{3,1},{4,1},{5,1},{6,1},{7,1},{8,1},],
      [{0,2},{1,2},{2,2},{3,2},{4,2},{5,2},{6,2},{7,2},{8,2},],
      [{0,3},{1,3},{2,3},{3,3},{4,3},{5,3},{6,3},{7,3},{8,3},],
      [{0,4},{1,4},{2,4},{3,4},{4,4},{5,4},{6,4},{7,4},{8,4},],
      [{0,5},{1,5},{2,5},{3,5},{4,5},{5,5},{6,5},{7,5},{8,5},],
      [{0,6},{1,6},{2,6},{3,6},{4,6},{5,6},{6,6},{7,6},{8,6},],
      [{0,7},{1,7},{2,7},{3,7},{4,7},{5,7},{6,7},{7,7},{8,7},],
      [{0,8},{1,8},{2,8},{3,8},{4,8},{5,8},{6,8},{7,8},{8,8},],
    ]
  end
end

Logic is the same for columns and boxes. Here you can code a bit to generate columns and boxes and run your code against my test file or check out my version here.
We will finish this part by the init function which will generate our empty board. We will basically map every coordinates with all the possibilities.

# board.ex
defmodule Sudoku.Board do
  ...
  def init do
    generate_rows
    |> List.flatten
    |> Enum.reduce(%{}, fn(tuple,acc) ->
    Map.put(acc, tuple, Integer.digits(123456789))
    end)
  end
end

Try it in iex !

iex -S mix
Sudoku.Board.init |> IO.inspect(limit: :infinity)
%{
    {3, 3} => [1, 2, 3, 4, 5, 6, 7, 8, 9], 
    {7, 6} => [1, 2, 3, 4, 5, 6, 7, 8, 9],
    {7, 8} => [1, 2, 3, 4, 5, 6, 7, 8, 9], 
    {4, 0} => [1, 2, 3, 4, 5, 6, 7, 8, 9],
    ...
    ...
    ...
    {8, 3} => [1, 2, 3, 4, 5, 6, 7, 8, 9], 
    {0, 4} => [1, 2, 3, 4, 5, 6, 7, 8, 9],
    {4, 4} => [1, 2, 3, 4, 5, 6, 7, 8, 9], 
    {4, 8} => [1, 2, 3, 4, 5, 6, 7, 8, 9],
    {5, 0} => [1, 2, 3, 4, 5, 6, 7, 8, 9], 
    {0, 1} => [1, 2, 3, 4, 5, 6, 7, 8, 9],
    {7, 3} => [1, 2, 3, 4, 5, 6, 7, 8, 9],
}

Applying values

source code

This is our puzzle: initial state of our sudoku

input_str = 003020600900305001001806400008102900700000008006708200002609500800203009005010300

We will create a map (input_map) that represents our input.

# data_structure_utils.ex
defmodule Sudoku.DataStructureUtils do
  def input_str_to_map(input_str) do
    l = String.length(input_str)
    if l !== 81, do: raise Sudoku.DataStructureUtils.BadInputLength
      Enum.reduce(0..(l-1), %{}, fn(n, acc) ->
      char = String.at(input_str, n)
      if char !== "0" do
        value = [String.to_integer(char)]
        Map.put(acc, {rem(n,9), div(n,9)}, value)
      else
        acc
      end
    end)
  end

  defmodule BadInputLength do
    defexception []
    def message(_), do: "Please provide an input with 81 numbers"
  end
end

then apply the input_map to our board

# strategies/apply_values.ex
def run(map, values) do
    Enum.reduce(Map.keys(values), map, fn(key, acc) ->
      Map.put(acc, key, Map.get(values, key))
    end)
end

Let's see that in action:

iex -S mix
input_map = Sudoku.DataStructureUtils.input_str_to_map("003020600900305001001806400008102900700000008006708200002609500800203009005010300")
Sudoku.ApplyValues.run(Sudoku.Board.init, input_map) |> IO.inspect(limit: :infinity)
%{
  {3, 3} => [1], 
  {4, 0} => [2],
  {2, 2} => [1],
  {2, 0} => [3], 
  {6, 3} => '\t', 
  {5, 7} => [3],
  {5, 1} => [5], 
  {0, 7} => '\b', 
  {8, 7} => '\t', 
  {3, 1} => [3], 
  {5, 6} => '\t', 
  {6, 2} => [4],
  {6, 8} => [3], 
  {3, 5} => '\a', 
  {2, 8} => [5],
  {3, 7} => [2], 
  {5, 2} => [6],
  {3, 6} => [6],
  {2, 3} => '\b', 
  {8, 1} => [1], 
  {8, 4} => '\b', 
  {5, 3} => [2],
  {2, 6} => [2], 
  {6, 6} => [5],
  {6, 0} => [6], 
  {6, 5} => [2],
  {5, 5} => '\b', 
  {3, 2} => '\b',
  {2, 5} => [6],
  {0, 4} => '\a', 
  {0, 1} => '\t',
  {4, 8} => [1],
  {7, 6} => [1, 2, 3, 4, 5, 6, 7, 8, 9],
  ... 
  ... 
  ... 
  {7, 3} => [1, 2, 3, 4, 5, 6, 7, 8, 9],
}

UGLY right ?! Let's fix that

Bonus: The Display module

source code

├── _build
...
├── config
│   └── config.exs
├── index.js
├── lib
│   ├── sudoku
│   │   ├── board.ex
│   │   ├── display.ex <--
│   │   ├── strategies
│   │   │   ├── apply_values.ex
│   └── sudoku.ex
├── mix.exs
├── package.json
├── README.md
├── stuff.js
└── test
    ├── sudoku_board_test.exs
    ├── sudoku_test.exs
    └── test_helper.exs

This part is not mandatory to run our project but if something goes wrong it's will be helpful !

If you want to code this part, one thing to know is that every item has to have the same size. For that you can use String.pad_trailing.
Then try to:

  • drawing a item
  • drawing a line of items
  • drawing a line of separation

If everything goes well, you will see a less ugly representation now ;) using iex

iex -S mix
input_map = Sudoku.DataStructureUtils.input_str_to_map("003020600900305001001806400008102900700000008006708200002609500800203009005010300")
Sudoku.ApplyValues.run(Sudoku.Board.init, input_map) |> Sudoku.Display.pretty(false)

That all for Part-1 ! And we don’t even solve a puzzle :(. In Part-2 we will start for real by using the backtracking algorithm.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment