diff --git a/config.json b/config.json index a83c3b6ea4..7fa52f0264 100644 --- a/config.json +++ b/config.json @@ -2733,6 +2733,32 @@ ], "practices": [], "difficulty": 7 + }, + { + "slug": "word-search", + "name": "Word Search", + "uuid": "41ab29d0-e094-4910-bb51-b99bdedc7f17", + "prerequisites": [ + "atoms", + "tuples", + "lists", + "enum", + "case", + "cond", + "if", + "multiple-clause-functions", + "pattern-matching", + "guards", + "strings", + "charlists", + "list-comprehensions", + "maps", + "structs" + ], + "practices": [ + "list-comprehensions" + ], + "difficulty": 7 } ], "foregone": [ diff --git a/exercises/practice/word-search/.docs/instructions.md b/exercises/practice/word-search/.docs/instructions.md new file mode 100644 index 0000000000..edd6392f20 --- /dev/null +++ b/exercises/practice/word-search/.docs/instructions.md @@ -0,0 +1,27 @@ +# Description + +In word search puzzles you get a square of letters and have to find specific +words in them. + +For example: + +```text +jefblpepre +camdcimgtc +oivokprjsm +pbwasqroua +rixilelhrs +wolcqlirpc +screeaumgr +alxhpburyi +jalaycalmp +clojurermt +``` + +There are several programming languages hidden in the above square. + +Words can be hidden in all kinds of directions: left-to-right, right-to-left, +vertical and diagonal. + +Given a puzzle and a list of words return the location of the first and last +letter of each word. diff --git a/exercises/practice/word-search/.formatter.exs b/exercises/practice/word-search/.formatter.exs new file mode 100644 index 0000000000..d2cda26edd --- /dev/null +++ b/exercises/practice/word-search/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/exercises/practice/word-search/.meta/config.json b/exercises/practice/word-search/.meta/config.json new file mode 100644 index 0000000000..d369d80576 --- /dev/null +++ b/exercises/practice/word-search/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": ["jiegillet"], + "contributors": [ + "neenjaw", + "angelikatyborska" + ], + "files": { + "example": [ + ".meta/example.ex" + ], + "solution": [ + "lib/word_search.ex" + ], + "test": [ + "test/word_search_test.exs" + ] + }, + "blurb": "Create a program to solve a word search puzzle." +} diff --git a/exercises/practice/word-search/.meta/example.ex b/exercises/practice/word-search/.meta/example.ex new file mode 100644 index 0000000000..b24d1a4a10 --- /dev/null +++ b/exercises/practice/word-search/.meta/example.ex @@ -0,0 +1,61 @@ +defmodule WordSearch do + defmodule Location do + defstruct [:from, :to] + + @type t :: %Location{ + from: %{row: integer, column: integer}, + to: %{row: integer, column: integer} + } + end + + @doc """ + Find the start and end positions of words in a grid of letters. + Row and column positions are 1 indexed. + """ + @spec search(grid :: String.t(), words :: [String.t()]) :: %{String.t() => nil | Location.t()} + def search(grid, words) do + grid = + String.split(grid, "\n") + |> Enum.map(&to_charlist/1) + + {coord, starting_points} = + for {row, r} <- Enum.with_index(grid, 1), + {char, c} <- Enum.with_index(row, 1) do + {char, %{row: r, column: c}} + end + |> Enum.reduce({%{}, %{}}, fn {char, pos}, {coord, starting_points} -> + {Map.put(coord, pos, char), Map.update(starting_points, char, [pos], &[pos | &1])} + end) + + Map.new(words, fn <> = word -> + {word, search(word, starting_points[start], coord)} + end) + end + + def search(_word, nil, _coord), do: nil + + def search(word, starting_points, coord) do + word_length = String.length(word) + word = to_charlist(word) + + for point <- starting_points, + path <- make_paths(point, word_length - 1), + word == Enum.map(path, &coord[&1]) do + %Location{from: List.first(path), to: List.last(path)} + end + |> Enum.reduce(nil, &||/2) + end + + def make_paths(%{row: r, column: c}, delta) do + [ + for(i <- 0..delta, do: %{row: r + i, column: c}), + for(i <- 0..delta, do: %{row: r - i, column: c}), + for(i <- 0..delta, do: %{row: r, column: c + i}), + for(i <- 0..delta, do: %{row: r, column: c - i}), + for(i <- 0..delta, do: %{row: r + i, column: c + i}), + for(i <- 0..delta, do: %{row: r + i, column: c - i}), + for(i <- 0..delta, do: %{row: r - i, column: c + i}), + for(i <- 0..delta, do: %{row: r - i, column: c - i}) + ] + end +end diff --git a/exercises/practice/word-search/.meta/tests.toml b/exercises/practice/word-search/.meta/tests.toml new file mode 100644 index 0000000000..d34f42f03c --- /dev/null +++ b/exercises/practice/word-search/.meta/tests.toml @@ -0,0 +1,69 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. +[b4057815-0d01-41f0-9119-6a91f54b2a0a] +description = "Should accept an initial game grid and a target search word" + +[6b22bcc5-6cbf-4674-931b-d2edbff73132] +description = "Should locate one word written left to right" + +[ff462410-434b-442d-9bc3-3360c75f34a8] +description = "Should locate the same word written left to right in a different position" + +[a02febae-6347-443e-b99c-ab0afb0b8fca] +description = "Should locate a different left to right word" + +[e42e9987-6304-4e13-8232-fa07d5280130] +description = "Should locate that different left to right word in a different position" + +[9bff3cee-49b9-4775-bdfb-d55b43a70b2f] +description = "Should locate a left to right word in two line grid" + +[851a35fb-f499-4ec1-9581-395a87903a22] +description = "Should locate a left to right word in three line grid" + +[2f3dcf84-ba7d-4b75-8b8d-a3672b32c035] +description = "Should locate a left to right word in ten line grid" + +[006d4856-f365-4e84-a18c-7d129ce9eefb] +description = "Should locate that left to right word in a different position in a ten line grid" + +[eff7ac9f-ff11-443e-9747-40850c12ab60] +description = "Should locate a different left to right word in a ten line grid" + +[dea39f86-8c67-4164-8884-13bfc48bd13b] +description = "Should locate multiple words" + +[29e6a6a5-f80c-48a6-8e68-05bbbe187a09] +description = "Should locate a single word written right to left" + +[3cf34428-b43f-48b6-b332-ea0b8836011d] +description = "Should locate multiple words written in different horizontal directions" + +[2c8cd344-a02f-464b-93b6-8bf1bd890003] +description = "Should locate words written top to bottom" + +[9ee1e43d-e59d-4c32-9a5f-6a22d4a1550f] +description = "Should locate words written bottom to top" + +[6a21a676-f59e-4238-8e88-9f81015afae9] +description = "Should locate words written top left to bottom right" + +[c9125189-1861-4b0d-a14e-ba5dab29ca7c] +description = "Should locate words written bottom right to top left" + +[b19e2149-7fc5-41ec-a8a9-9bc6c6c38c40] +description = "Should locate words written bottom left to top right" + +[69e1d994-a6d7-4e24-9b5a-db76751c2ef8] +description = "Should locate words written top right to bottom left" + +[695531db-69eb-463f-8bad-8de3bf5ef198] +description = "Should fail to locate a word that is not in the puzzle" diff --git a/exercises/practice/word-search/lib/word_search.ex b/exercises/practice/word-search/lib/word_search.ex new file mode 100644 index 0000000000..81a1f56639 --- /dev/null +++ b/exercises/practice/word-search/lib/word_search.ex @@ -0,0 +1,18 @@ +defmodule WordSearch do + defmodule Location do + defstruct [:from, :to] + + @type t :: %Location{ + from: %{row: integer, column: integer}, + to: %{row: integer, column: integer} + } + end + + @doc """ + Find the start and end positions of words in a grid of letters. + Row and column positions are 1 indexed. + """ + @spec search(grid :: String.t(), words :: [String.t()]) :: %{String.t() => nil | Location.t()} + def search(grid, words) do + end +end diff --git a/exercises/practice/word-search/mix.exs b/exercises/practice/word-search/mix.exs new file mode 100644 index 0000000000..1a4069e960 --- /dev/null +++ b/exercises/practice/word-search/mix.exs @@ -0,0 +1,28 @@ +defmodule WordSearch.MixProject do + use Mix.Project + + def project do + [ + app: :word_search, + version: "0.1.0", + # elixir: "~> 1.8", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger] + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + # {:dep_from_hexpm, "~> 0.3.0"}, + # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} + ] + end +end diff --git a/exercises/practice/word-search/test/test_helper.exs b/exercises/practice/word-search/test/test_helper.exs new file mode 100644 index 0000000000..35fc5bff82 --- /dev/null +++ b/exercises/practice/word-search/test/test_helper.exs @@ -0,0 +1,2 @@ +ExUnit.start() +ExUnit.configure(exclude: :pending, trace: true) diff --git a/exercises/practice/word-search/test/word_search_test.exs b/exercises/practice/word-search/test/word_search_test.exs new file mode 100644 index 0000000000..ad593f7e41 --- /dev/null +++ b/exercises/practice/word-search/test/word_search_test.exs @@ -0,0 +1,463 @@ +defmodule WordSearchTest do + use ExUnit.Case + alias WordSearch.Location + + # Grid rows and columns are 1-indexed. + # @tag :pending + test "Should accept an initial game grid and a target search word" do + grid = "jefblpepre" + words = ["clojure"] + output = WordSearch.search(grid, words) + expected = %{"clojure" => nil} + + assert output == expected + end + + @tag :pending + test "Should locate one word written left to right" do + grid = "clojurermt" + words = ["clojure"] + output = WordSearch.search(grid, words) + + expected = %{ + "clojure" => %Location{from: %{row: 1, column: 1}, to: %{row: 1, column: 7}} + } + + assert output == expected + end + + @tag :pending + test "Should locate the same word written left to right in a different position" do + grid = "mtclojurer" + words = ["clojure"] + output = WordSearch.search(grid, words) + + expected = %{ + "clojure" => %Location{from: %{row: 1, column: 3}, to: %{row: 1, column: 9}} + } + + assert output == expected + end + + @tag :pending + test "Should locate a different left to right word" do + grid = "coffeelplx" + words = ["coffee"] + output = WordSearch.search(grid, words) + + expected = %{ + "coffee" => %Location{from: %{row: 1, column: 1}, to: %{row: 1, column: 6}} + } + + assert output == expected + end + + @tag :pending + test "Should locate that different left to right word in a different position" do + grid = "xcoffeezlp" + words = ["coffee"] + output = WordSearch.search(grid, words) + + expected = %{ + "coffee" => %Location{from: %{row: 1, column: 2}, to: %{row: 1, column: 7}} + } + + assert output == expected + end + + @tag :pending + test "Should locate a left to right word in two line grid" do + grid = """ + jefblpepre + tclojurerm + """ + + words = ["clojure"] + output = WordSearch.search(grid, words) + + expected = %{ + "clojure" => %Location{from: %{row: 2, column: 2}, to: %{row: 2, column: 8}} + } + + assert output == expected + end + + @tag :pending + test "Should locate a left to right word in three line grid" do + grid = """ + camdcimgtc + jefblpepre + clojurermt + """ + + words = ["clojure"] + output = WordSearch.search(grid, words) + + expected = %{ + "clojure" => %Location{from: %{row: 3, column: 1}, to: %{row: 3, column: 7}} + } + + assert output == expected + end + + @tag :pending + test "Should locate a left to right word in ten line grid" do + grid = """ + jefblpepre + camdcimgtc + oivokprjsm + pbwasqroua + rixilelhrs + wolcqlirpc + screeaumgr + alxhpburyi + jalaycalmp + clojurermt + """ + + words = ["clojure"] + output = WordSearch.search(grid, words) + + expected = %{ + "clojure" => %Location{from: %{row: 10, column: 1}, to: %{row: 10, column: 7}} + } + + assert output == expected + end + + @tag :pending + test "Should locate that left to right word in a different position in a ten line grid" do + grid = """ + jefblpepre + camdcimgtc + oivokprjsm + pbwasqroua + rixilelhrs + wolcqlirpc + screeaumgr + alxhpburyi + clojurermt + jalaycalmp + """ + + words = ["clojure"] + output = WordSearch.search(grid, words) + + expected = %{ + "clojure" => %Location{from: %{row: 9, column: 1}, to: %{row: 9, column: 7}} + } + + assert output == expected + end + + @tag :pending + test "Should locate a different left to right word in a ten line grid" do + grid = """ + jefblpepre + camdcimgtc + oivokprjsm + pbwasqroua + rixilelhrs + wolcqlirpc + fortranftw + alxhpburyi + clojurermt + jalaycalmp + """ + + words = ["fortran"] + output = WordSearch.search(grid, words) + + expected = %{ + "fortran" => %Location{from: %{row: 7, column: 1}, to: %{row: 7, column: 7}} + } + + assert output == expected + end + + @tag :pending + test "Should locate multiple words" do + grid = """ + jefblpepre + camdcimgtc + oivokprjsm + pbwasqroua + rixilelhrs + wolcqlirpc + fortranftw + alxhpburyi + jalaycalmp + clojurermt + """ + + words = ["fortran", "clojure"] + output = WordSearch.search(grid, words) + + expected = %{ + "clojure" => %Location{from: %{row: 10, column: 1}, to: %{row: 10, column: 7}}, + "fortran" => %Location{from: %{row: 7, column: 1}, to: %{row: 7, column: 7}} + } + + assert output == expected + end + + @tag :pending + test "Should locate a single word written right to left" do + grid = "rixilelhrs" + words = ["elixir"] + output = WordSearch.search(grid, words) + + expected = %{ + "elixir" => %Location{from: %{row: 1, column: 6}, to: %{row: 1, column: 1}} + } + + assert output == expected + end + + @tag :pending + test "Should locate multiple words written in different horizontal directions" do + grid = """ + jefblpepre + camdcimgtc + oivokprjsm + pbwasqroua + rixilelhrs + wolcqlirpc + screeaumgr + alxhpburyi + jalaycalmp + clojurermt + """ + + words = ["elixir", "clojure"] + output = WordSearch.search(grid, words) + + expected = %{ + "clojure" => %Location{from: %{row: 10, column: 1}, to: %{row: 10, column: 7}}, + "elixir" => %Location{from: %{row: 5, column: 6}, to: %{row: 5, column: 1}} + } + + assert output == expected + end + + @tag :pending + test "Should locate words written top to bottom" do + grid = """ + jefblpepre + camdcimgtc + oivokprjsm + pbwasqroua + rixilelhrs + wolcqlirpc + screeaumgr + alxhpburyi + jalaycalmp + clojurermt + """ + + words = ["clojure", "elixir", "ecmascript"] + output = WordSearch.search(grid, words) + + expected = %{ + "clojure" => %Location{from: %{row: 10, column: 1}, to: %{row: 10, column: 7}}, + "ecmascript" => %Location{from: %{row: 1, column: 10}, to: %{row: 10, column: 10}}, + "elixir" => %Location{from: %{row: 5, column: 6}, to: %{row: 5, column: 1}} + } + + assert output == expected + end + + @tag :pending + test "Should locate words written bottom to top" do + grid = """ + jefblpepre + camdcimgtc + oivokprjsm + pbwasqroua + rixilelhrs + wolcqlirpc + screeaumgr + alxhpburyi + jalaycalmp + clojurermt + """ + + words = ["clojure", "elixir", "ecmascript", "rust"] + output = WordSearch.search(grid, words) + + expected = %{ + "clojure" => %Location{from: %{row: 10, column: 1}, to: %{row: 10, column: 7}}, + "ecmascript" => %Location{from: %{row: 1, column: 10}, to: %{row: 10, column: 10}}, + "elixir" => %Location{from: %{row: 5, column: 6}, to: %{row: 5, column: 1}}, + "rust" => %Location{from: %{row: 5, column: 9}, to: %{row: 2, column: 9}} + } + + assert output == expected + end + + @tag :pending + test "Should locate words written top left to bottom right" do + grid = """ + jefblpepre + camdcimgtc + oivokprjsm + pbwasqroua + rixilelhrs + wolcqlirpc + screeaumgr + alxhpburyi + jalaycalmp + clojurermt + """ + + words = ["clojure", "elixir", "ecmascript", "rust", "java"] + output = WordSearch.search(grid, words) + + expected = %{ + "clojure" => %Location{from: %{row: 10, column: 1}, to: %{row: 10, column: 7}}, + "ecmascript" => %Location{from: %{row: 1, column: 10}, to: %{row: 10, column: 10}}, + "elixir" => %Location{from: %{row: 5, column: 6}, to: %{row: 5, column: 1}}, + "java" => %Location{from: %{row: 1, column: 1}, to: %{row: 4, column: 4}}, + "rust" => %Location{from: %{row: 5, column: 9}, to: %{row: 2, column: 9}} + } + + assert output == expected + end + + @tag :pending + test "Should locate words written bottom right to top left" do + grid = """ + jefblpepre + camdcimgtc + oivokprjsm + pbwasqroua + rixilelhrs + wolcqlirpc + screeaumgr + alxhpburyi + jalaycalmp + clojurermt + """ + + words = ["clojure", "elixir", "ecmascript", "rust", "java", "lua"] + output = WordSearch.search(grid, words) + + expected = %{ + "clojure" => %Location{from: %{row: 10, column: 1}, to: %{row: 10, column: 7}}, + "ecmascript" => %Location{from: %{row: 1, column: 10}, to: %{row: 10, column: 10}}, + "elixir" => %Location{from: %{row: 5, column: 6}, to: %{row: 5, column: 1}}, + "java" => %Location{from: %{row: 1, column: 1}, to: %{row: 4, column: 4}}, + "lua" => %Location{from: %{row: 9, column: 8}, to: %{row: 7, column: 6}}, + "rust" => %Location{from: %{row: 5, column: 9}, to: %{row: 2, column: 9}} + } + + assert output == expected + end + + @tag :pending + test "Should locate words written bottom left to top right" do + grid = """ + jefblpepre + camdcimgtc + oivokprjsm + pbwasqroua + rixilelhrs + wolcqlirpc + screeaumgr + alxhpburyi + jalaycalmp + clojurermt + """ + + words = ["clojure", "elixir", "ecmascript", "rust", "java", "lua", "lisp"] + output = WordSearch.search(grid, words) + + expected = %{ + "clojure" => %Location{from: %{row: 10, column: 1}, to: %{row: 10, column: 7}}, + "ecmascript" => %Location{from: %{row: 1, column: 10}, to: %{row: 10, column: 10}}, + "elixir" => %Location{from: %{row: 5, column: 6}, to: %{row: 5, column: 1}}, + "java" => %Location{from: %{row: 1, column: 1}, to: %{row: 4, column: 4}}, + "lisp" => %Location{from: %{row: 6, column: 3}, to: %{row: 3, column: 6}}, + "lua" => %Location{from: %{row: 9, column: 8}, to: %{row: 7, column: 6}}, + "rust" => %Location{from: %{row: 5, column: 9}, to: %{row: 2, column: 9}} + } + + assert output == expected + end + + @tag :pending + test "Should locate words written top right to bottom left" do + grid = """ + jefblpepre + camdcimgtc + oivokprjsm + pbwasqroua + rixilelhrs + wolcqlirpc + screeaumgr + alxhpburyi + jalaycalmp + clojurermt + """ + + words = ["clojure", "elixir", "ecmascript", "rust", "java", "lua", "lisp", "ruby"] + output = WordSearch.search(grid, words) + + expected = %{ + "clojure" => %Location{from: %{row: 10, column: 1}, to: %{row: 10, column: 7}}, + "ecmascript" => %Location{from: %{row: 1, column: 10}, to: %{row: 10, column: 10}}, + "elixir" => %Location{from: %{row: 5, column: 6}, to: %{row: 5, column: 1}}, + "java" => %Location{from: %{row: 1, column: 1}, to: %{row: 4, column: 4}}, + "lisp" => %Location{from: %{row: 6, column: 3}, to: %{row: 3, column: 6}}, + "lua" => %Location{from: %{row: 9, column: 8}, to: %{row: 7, column: 6}}, + "ruby" => %Location{from: %{row: 6, column: 8}, to: %{row: 9, column: 5}}, + "rust" => %Location{from: %{row: 5, column: 9}, to: %{row: 2, column: 9}} + } + + assert output == expected + end + + @tag :pending + test "Should fail to locate a word that is not in the puzzle" do + grid = """ + jefblpepre + camdcimgtc + oivokprjsm + pbwasqroua + rixilelhrs + wolcqlirpc + screeaumgr + alxhpburyi + jalaycalmp + clojurermt + """ + + words = [ + "clojure", + "elixir", + "ecmascript", + "rust", + "java", + "lua", + "lisp", + "ruby", + "haskell" + ] + + output = WordSearch.search(grid, words) + + expected = %{ + "clojure" => %Location{from: %{row: 10, column: 1}, to: %{row: 10, column: 7}}, + "ecmascript" => %Location{from: %{row: 1, column: 10}, to: %{row: 10, column: 10}}, + "elixir" => %Location{from: %{row: 5, column: 6}, to: %{row: 5, column: 1}}, + "haskell" => nil, + "java" => %Location{from: %{row: 1, column: 1}, to: %{row: 4, column: 4}}, + "lisp" => %Location{from: %{row: 6, column: 3}, to: %{row: 3, column: 6}}, + "lua" => %Location{from: %{row: 9, column: 8}, to: %{row: 7, column: 6}}, + "ruby" => %Location{from: %{row: 6, column: 8}, to: %{row: 9, column: 5}}, + "rust" => %Location{from: %{row: 5, column: 9}, to: %{row: 2, column: 9}} + } + + assert output == expected + end +end