From e469b45491d818e070e0307543637b89ea70f6cf Mon Sep 17 00:00:00 2001 From: Xevion Date: Mon, 18 Jan 2021 22:45:34 -0600 Subject: [PATCH] nucleotide count + robot simulator solutions elixir --- .../nucleotide-count/.exercism/metadata.json | 1 + elixir/nucleotide-count/.formatter.exs | 4 + elixir/nucleotide-count/.gitignore | 24 +++++ elixir/nucleotide-count/README.md | 55 +++++++++++ .../nucleotide-count/lib/nucleotide_count.ex | 32 +++++++ elixir/nucleotide-count/mix.exs | 28 ++++++ .../test/nucleotide_count_test.exs | 37 ++++++++ elixir/nucleotide-count/test/test_helper.exs | 2 + .../robot-simulator/.exercism/metadata.json | 1 + elixir/robot-simulator/.formatter.exs | 4 + elixir/robot-simulator/.gitignore | 24 +++++ elixir/robot-simulator/README.md | 70 ++++++++++++++ elixir/robot-simulator/lib/robot_simulator.ex | 93 +++++++++++++++++++ elixir/robot-simulator/mix.exs | 28 ++++++ .../test/robot_simulator_test.exs | 74 +++++++++++++++ elixir/robot-simulator/test/test_helper.exs | 2 + 16 files changed, 479 insertions(+) create mode 100644 elixir/nucleotide-count/.exercism/metadata.json create mode 100644 elixir/nucleotide-count/.formatter.exs create mode 100644 elixir/nucleotide-count/.gitignore create mode 100644 elixir/nucleotide-count/README.md create mode 100644 elixir/nucleotide-count/lib/nucleotide_count.ex create mode 100644 elixir/nucleotide-count/mix.exs create mode 100644 elixir/nucleotide-count/test/nucleotide_count_test.exs create mode 100644 elixir/nucleotide-count/test/test_helper.exs create mode 100644 elixir/robot-simulator/.exercism/metadata.json create mode 100644 elixir/robot-simulator/.formatter.exs create mode 100644 elixir/robot-simulator/.gitignore create mode 100644 elixir/robot-simulator/README.md create mode 100644 elixir/robot-simulator/lib/robot_simulator.ex create mode 100644 elixir/robot-simulator/mix.exs create mode 100644 elixir/robot-simulator/test/robot_simulator_test.exs create mode 100644 elixir/robot-simulator/test/test_helper.exs diff --git a/elixir/nucleotide-count/.exercism/metadata.json b/elixir/nucleotide-count/.exercism/metadata.json new file mode 100644 index 0000000..7d29625 --- /dev/null +++ b/elixir/nucleotide-count/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"elixir","exercise":"nucleotide-count","id":"651779260530498397cb25e2a1209466","url":"https://exercism.io/my/solutions/651779260530498397cb25e2a1209466","handle":"Xevion","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/elixir/nucleotide-count/.formatter.exs b/elixir/nucleotide-count/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/elixir/nucleotide-count/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/elixir/nucleotide-count/.gitignore b/elixir/nucleotide-count/.gitignore new file mode 100644 index 0000000..2989ff0 --- /dev/null +++ b/elixir/nucleotide-count/.gitignore @@ -0,0 +1,24 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +nucleotide_count-*.tar + diff --git a/elixir/nucleotide-count/README.md b/elixir/nucleotide-count/README.md new file mode 100644 index 0000000..8b0fe66 --- /dev/null +++ b/elixir/nucleotide-count/README.md @@ -0,0 +1,55 @@ +# Nucleotide Count + +Given a single stranded DNA string, compute how many times each nucleotide occurs in the string. + +The genetic language of every living thing on the planet is DNA. +DNA is a large molecule that is built from an extremely long sequence of individual elements called nucleotides. +4 types exist in DNA and these differ only slightly and can be represented as the following symbols: 'A' for adenine, 'C' for cytosine, 'G' for guanine, and 'T' thymine. + +Here is an analogy: +- twigs are to birds nests as +- nucleotides are to DNA as +- legos are to lego houses as +- words are to sentences as... + +## Running tests + +Execute the tests with: + +```bash +$ mix test +``` + +### Pending tests + +In the test suites, all but the first test have been skipped. + +Once you get a test passing, you can unskip the next one by +commenting out the relevant `@tag :pending` with a `#` symbol. + +For example: + +```elixir +# @tag :pending +test "shouting" do + assert Bob.hey("WATCH OUT!") == "Whoa, chill out!" +end +``` + +Or, you can enable all the tests by commenting out the +`ExUnit.configure` line in the test suite. + +```elixir +# ExUnit.configure exclude: :pending, trace: true +``` + +If you're stuck on something, it may help to look at some of +the [available resources](https://exercism.io/tracks/elixir/resources) +out there where answers might be found. + +## Source + +The Calculating DNA Nucleotides_problem at Rosalind [http://rosalind.info/problems/dna/](http://rosalind.info/problems/dna/) + +## Submitting Incomplete Solutions +It's possible to submit an incomplete solution so you can see how others have completed the exercise. diff --git a/elixir/nucleotide-count/lib/nucleotide_count.ex b/elixir/nucleotide-count/lib/nucleotide_count.ex new file mode 100644 index 0000000..dac3e97 --- /dev/null +++ b/elixir/nucleotide-count/lib/nucleotide_count.ex @@ -0,0 +1,32 @@ +defmodule NucleotideCount do + @doc """ + Counts individual nucleotides in a DNA strand. + + ## Examples + + iex> NucleotideCount.count('AATAA', ?A) + 4 + + iex> NucleotideCount.count('AATAA', ?T) + 1 + """ + @spec count(charlist(), char()) :: non_neg_integer() + def count(strand, nucleotide) do + Enum.count(strand, &(&1 == nucleotide)) + end + + @doc """ + Returns a summary of counts by nucleotide. + + ## Examples + + iex> NucleotideCount.histogram('AATAA') + %{?A => 4, ?T => 1, ?C => 0, ?G => 0} + """ + @spec histogram(charlist()) :: map() + def histogram(strand) do + Enum.reduce(strand, %{?A => 0, ?T => 0, ?C => 0, ?G => 0}, fn nucleotide, counts -> + Map.update(counts, nucleotide, 1, &(&1 + 1)) + end) + end +end diff --git a/elixir/nucleotide-count/mix.exs b/elixir/nucleotide-count/mix.exs new file mode 100644 index 0000000..a22da50 --- /dev/null +++ b/elixir/nucleotide-count/mix.exs @@ -0,0 +1,28 @@ +defmodule NucleotideCount.MixProject do + use Mix.Project + + def project do + [ + app: :nucleotide_count, + 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/elixir/nucleotide-count/test/nucleotide_count_test.exs b/elixir/nucleotide-count/test/nucleotide_count_test.exs new file mode 100644 index 0000000..3a3e68f --- /dev/null +++ b/elixir/nucleotide-count/test/nucleotide_count_test.exs @@ -0,0 +1,37 @@ +defmodule NucleotideCountTest do + use ExUnit.Case + + # @tag :pending + test "empty dna string has no adenine" do + assert NucleotideCount.count('', ?A) == 0 + end + + @tag :pending + test "repetitive cytosine gets counted" do + assert NucleotideCount.count('CCCCC', ?C) == 5 + end + + @tag :pending + test "counts only thymine" do + assert NucleotideCount.count('GGGGGTAACCCGG', ?T) == 1 + end + + @tag :pending + test "empty dna string has no nucleotides" do + expected = %{?A => 0, ?T => 0, ?C => 0, ?G => 0} + assert NucleotideCount.histogram('') == expected + end + + @tag :pending + test "repetitive sequence has only guanine" do + expected = %{?A => 0, ?T => 0, ?C => 0, ?G => 8} + assert NucleotideCount.histogram('GGGGGGGG') == expected + end + + @tag :pending + test "counts all nucleotides" do + s = 'AGCTTTTCATTCTGACTGCAACGGGCAATATGTCTCTGTGTGGATTAAAAAAAGAGTGTCTGATAGCAGC' + expected = %{?A => 20, ?T => 21, ?C => 12, ?G => 17} + assert NucleotideCount.histogram(s) == expected + end +end diff --git a/elixir/nucleotide-count/test/test_helper.exs b/elixir/nucleotide-count/test/test_helper.exs new file mode 100644 index 0000000..0ee8618 --- /dev/null +++ b/elixir/nucleotide-count/test/test_helper.exs @@ -0,0 +1,2 @@ +ExUnit.start() +# ExUnit.configure(exclude: :pending, trace: true) diff --git a/elixir/robot-simulator/.exercism/metadata.json b/elixir/robot-simulator/.exercism/metadata.json new file mode 100644 index 0000000..4f7a609 --- /dev/null +++ b/elixir/robot-simulator/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"elixir","exercise":"robot-simulator","id":"a8c52ea3878944528a085201a24226e4","url":"https://exercism.io/my/solutions/a8c52ea3878944528a085201a24226e4","handle":"Xevion","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/elixir/robot-simulator/.formatter.exs b/elixir/robot-simulator/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/elixir/robot-simulator/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/elixir/robot-simulator/.gitignore b/elixir/robot-simulator/.gitignore new file mode 100644 index 0000000..4d81f30 --- /dev/null +++ b/elixir/robot-simulator/.gitignore @@ -0,0 +1,24 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +robot_simulator-*.tar + diff --git a/elixir/robot-simulator/README.md b/elixir/robot-simulator/README.md new file mode 100644 index 0000000..1b97756 --- /dev/null +++ b/elixir/robot-simulator/README.md @@ -0,0 +1,70 @@ +# Robot Simulator + +Write a robot simulator. + +A robot factory's test facility needs a program to verify robot movements. + +The robots have three possible movements: + +- turn right +- turn left +- advance + +Robots are placed on a hypothetical infinite grid, facing a particular +direction (north, east, south, or west) at a set of {x,y} coordinates, +e.g., {3,8}, with coordinates increasing to the north and east. + +The robot then receives a number of instructions, at which point the +testing facility verifies the robot's new position, and in which +direction it is pointing. + +- The letter-string "RAALAL" means: + - Turn right + - Advance twice + - Turn left + - Advance once + - Turn left yet again +- Say a robot starts at {7, 3} facing north. Then running this stream + of instructions should leave it at {9, 4} facing west. + +## Running tests + +Execute the tests with: + +```bash +$ mix test +``` + +### Pending tests + +In the test suites, all but the first test have been skipped. + +Once you get a test passing, you can unskip the next one by +commenting out the relevant `@tag :pending` with a `#` symbol. + +For example: + +```elixir +# @tag :pending +test "shouting" do + assert Bob.hey("WATCH OUT!") == "Whoa, chill out!" +end +``` + +Or, you can enable all the tests by commenting out the +`ExUnit.configure` line in the test suite. + +```elixir +# ExUnit.configure exclude: :pending, trace: true +``` + +If you're stuck on something, it may help to look at some of +the [available resources](https://exercism.io/tracks/elixir/resources) +out there where answers might be found. + +## Source + +Inspired by an interview question at a famous company. + +## Submitting Incomplete Solutions +It's possible to submit an incomplete solution so you can see how others have completed the exercise. diff --git a/elixir/robot-simulator/lib/robot_simulator.ex b/elixir/robot-simulator/lib/robot_simulator.ex new file mode 100644 index 0000000..1998aa7 --- /dev/null +++ b/elixir/robot-simulator/lib/robot_simulator.ex @@ -0,0 +1,93 @@ +defmodule RobotSimulator do + defmodule Robot, do: defstruct([:position, :direction]) + + @directions [:north, :east, :south, :west] + @turns %{"R" => 1, "L" => -1} + + defguardp is_direction(direction) when direction in [:north, :east, :south, :west] + + defguardp is_position(position) + when is_tuple(position) and tuple_size(position) == 2 and + is_integer(elem(position, 0)) and is_integer(elem(position, 1)) + + @doc """ + Create a Robot Simulator given an initial direction and position. + + Valid directions are: `:north`, `:east`, `:south`, `:west` + """ + @spec create(direction :: atom, position :: {integer, integer}) :: any + def create(direction \\ :north, position \\ {0, 0}) + + def create(direction, _position) when not is_direction(direction), + do: {:error, "invalid direction"} + + def create(_direction, position) when not is_position(position), + do: {:error, "invalid position"} + + def create(direction, position) do + %Robot{position: position, direction: direction} + end + + @doc """ + Simulate the robot's movement given a string of instructions. + + Valid instructions are: "R" (turn right), "L", (turn left), and "A" (advance) + """ + @spec simulate(robot :: any, instructions :: String.t()) :: any + def simulate(robot, ""), do: robot + + def simulate( + %Robot{position: position, direction: direction} = robot, + <> <> tail + ) do + # {head, tail} = String.split_at(instructions, 1) + + case head do + "A" -> + # Map.get_and_update!(robot, :position, &{&1, get_change(&1, robot.direction)}) + simulate(%Robot{robot | position: get_change(position, direction)}, tail) + + "L" -> + simulate(%Robot{robot | direction: get_turn(head, direction)}, tail) + + "R" -> + simulate(%Robot{robot | direction: get_turn(head, direction)}, tail) + + _ -> + {:error, "invalid instruction"} + end + end + + @doc """ + Return the robot's direction. + + Valid directions are: `:north`, `:east`, `:south`, `:west` + """ + @spec direction(robot :: any) :: atom + def direction(robot) do + robot.direction + end + + @doc """ + Return the robot's position. + """ + @spec position(robot :: any) :: {integer, integer} + def position(robot) do + robot.position + end + + def get_turn(turn, direction) do + @directions + |> Enum.fetch(rem(Enum.find_index(@directions, &(&1 == direction)) + @turns[turn], 4)) + |> elem(1) + end + + def get_change({x, y}, direction) do + case direction do + :north -> {x, y + 1} + :east -> {x + 1, y} + :south -> {x, y - 1} + :west -> {x - 1, y} + end + end +end diff --git a/elixir/robot-simulator/mix.exs b/elixir/robot-simulator/mix.exs new file mode 100644 index 0000000..6c3e8f3 --- /dev/null +++ b/elixir/robot-simulator/mix.exs @@ -0,0 +1,28 @@ +defmodule RobotSimulator.MixProject do + use Mix.Project + + def project do + [ + app: :robot_simulator, + 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/elixir/robot-simulator/test/robot_simulator_test.exs b/elixir/robot-simulator/test/robot_simulator_test.exs new file mode 100644 index 0000000..6208680 --- /dev/null +++ b/elixir/robot-simulator/test/robot_simulator_test.exs @@ -0,0 +1,74 @@ +defmodule RobotSimulatorTest do + use ExUnit.Case + + test "create has sensible defaults" do + robot = RobotSimulator.create() + assert RobotSimulator.position(robot) == {0, 0} + assert RobotSimulator.direction(robot) == :north + end + + @tag :pending + test "create works with valid arguments" do + robot = RobotSimulator.create(:north, {0, 0}) + assert RobotSimulator.position(robot) == {0, 0} + assert RobotSimulator.direction(robot) == :north + + robot = RobotSimulator.create(:south, {-10, 0}) + assert RobotSimulator.position(robot) == {-10, 0} + assert RobotSimulator.direction(robot) == :south + + robot = RobotSimulator.create(:east, {0, 10}) + assert RobotSimulator.position(robot) == {0, 10} + assert RobotSimulator.direction(robot) == :east + + robot = RobotSimulator.create(:west, {100, -100}) + assert RobotSimulator.position(robot) == {100, -100} + assert RobotSimulator.direction(robot) == :west + end + + @tag :pending + test "create errors if invalid direction given" do + position = {0, 0} + invalid_direction = {:error, "invalid direction"} + + assert RobotSimulator.create(:invalid, position) == invalid_direction + assert RobotSimulator.create(0, position) == invalid_direction + assert RobotSimulator.create("east", position) == invalid_direction + end + + @tag :pending + test "create errors if invalid position given" do + direction = :north + invalid_position = {:error, "invalid position"} + + assert RobotSimulator.create(direction, {0, 0, 0}) == invalid_position + assert RobotSimulator.create(direction, {0, :invalid}) == invalid_position + assert RobotSimulator.create(direction, {"0", 0}) == invalid_position + + assert RobotSimulator.create(direction, "invalid") == invalid_position + assert RobotSimulator.create(direction, 0) == invalid_position + assert RobotSimulator.create(direction, [0, 0]) == invalid_position + assert RobotSimulator.create(direction, nil) == invalid_position + end + + @tag :pending + test "simulate robots" do + robot1 = RobotSimulator.create(:north, {0, 0}) |> RobotSimulator.simulate("LAAARALA") + assert RobotSimulator.direction(robot1) == :west + assert RobotSimulator.position(robot1) == {-4, 1} + + robot2 = RobotSimulator.create(:east, {2, -7}) |> RobotSimulator.simulate("RRAAAAALA") + assert RobotSimulator.direction(robot2) == :south + assert RobotSimulator.position(robot2) == {-3, -8} + + robot3 = RobotSimulator.create(:south, {8, 4}) |> RobotSimulator.simulate("LAAARRRALLLL") + assert RobotSimulator.direction(robot3) == :north + assert RobotSimulator.position(robot3) == {11, 5} + end + + @tag :pending + test "simulate errors on invalid instructions" do + assert RobotSimulator.create() |> RobotSimulator.simulate("UUDDLRLRBASTART") == + {:error, "invalid instruction"} + end +end diff --git a/elixir/robot-simulator/test/test_helper.exs b/elixir/robot-simulator/test/test_helper.exs new file mode 100644 index 0000000..0ee8618 --- /dev/null +++ b/elixir/robot-simulator/test/test_helper.exs @@ -0,0 +1,2 @@ +ExUnit.start() +# ExUnit.configure(exclude: :pending, trace: true)