mirror of
https://github.com/rjNemo/exercism-elixir
synced 2026-06-06 02:16:48 +00:00
acronym
This commit is contained in:
parent
dba21bcf5c
commit
c43da91eb3
31 changed files with 1230 additions and 0 deletions
35
acronym/.exercism/config.json
Normal file
35
acronym/.exercism/config.json
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
{
|
||||||
|
"blurb": "Convert a long phrase to its acronym.",
|
||||||
|
"authors": [
|
||||||
|
"Teapane"
|
||||||
|
],
|
||||||
|
"contributors": [
|
||||||
|
"angelikatyborska",
|
||||||
|
"Cohen-Carlisle",
|
||||||
|
"dalexj",
|
||||||
|
"devonestes",
|
||||||
|
"gmile",
|
||||||
|
"henrik",
|
||||||
|
"jwworth",
|
||||||
|
"lpil",
|
||||||
|
"martinsvalin",
|
||||||
|
"neenjaw",
|
||||||
|
"parkerl",
|
||||||
|
"rubysolo",
|
||||||
|
"sotojuan",
|
||||||
|
"waiting-for-dev"
|
||||||
|
],
|
||||||
|
"files": {
|
||||||
|
"solution": [
|
||||||
|
"lib/acronym.ex"
|
||||||
|
],
|
||||||
|
"test": [
|
||||||
|
"test/acronym_test.exs"
|
||||||
|
],
|
||||||
|
"example": [
|
||||||
|
".meta/example.ex"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"source": "Julien Vanier",
|
||||||
|
"source_url": "https://github.com/monkbroc"
|
||||||
|
}
|
||||||
1
acronym/.exercism/metadata.json
Normal file
1
acronym/.exercism/metadata.json
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"track":"elixir","exercise":"acronym","id":"f5a4349b2c8d414a9f8ad6e261be1847","url":"https://exercism.org/tracks/elixir/exercises/acronym","handle":"rjNemo","is_requester":true,"auto_approve":false}
|
||||||
4
acronym/.formatter.exs
Normal file
4
acronym/.formatter.exs
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
# Used by "mix format"
|
||||||
|
[
|
||||||
|
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
||||||
|
]
|
||||||
24
acronym/.gitignore
vendored
Normal file
24
acronym/.gitignore
vendored
Normal file
|
|
@ -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").
|
||||||
|
acronym-*.tar
|
||||||
|
|
||||||
75
acronym/HELP.md
Normal file
75
acronym/HELP.md
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
# Help
|
||||||
|
|
||||||
|
## Running the tests
|
||||||
|
|
||||||
|
From the terminal, change to the base directory of the exercise then execute the tests with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ mix test
|
||||||
|
```
|
||||||
|
|
||||||
|
This will execute the test file found in the `test` subfolder -- a file ending in `_test.exs`
|
||||||
|
|
||||||
|
Documentation:
|
||||||
|
|
||||||
|
* [`mix test` - Elixir's test execution tool](https://hexdocs.pm/mix/Mix.Tasks.Test.html)
|
||||||
|
* [`ExUnit` - Elixir's unit test library](https://hexdocs.pm/ex_unit/ExUnit.html)
|
||||||
|
|
||||||
|
## Pending tests
|
||||||
|
|
||||||
|
In test suites of practice exercises, all but the first test have been tagged to be 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
|
||||||
|
```
|
||||||
|
|
||||||
|
If you wish to run all tests at once, you can include all skipped test by using the `--include` flag on the `mix test` command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ mix test --include pending
|
||||||
|
```
|
||||||
|
|
||||||
|
Or, you can enable all the tests by commenting out the `ExUnit.configure` line in the file `test/test_helper.exs`.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
# ExUnit.configure(exclude: :pending, trace: true)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Useful `mix test` options
|
||||||
|
|
||||||
|
* `test/<FILE>.exs:LINENUM` - runs only a single test, the test from `<FILE>.exs` whose definition is on line `LINENUM`
|
||||||
|
* `--failed` - runs only tests that failed the last time they ran
|
||||||
|
* `--max-failures` - the suite stops evaluating tests when this number of test failures
|
||||||
|
is reached
|
||||||
|
* `--seed 0` - disables randomization so the tests in a single file will always be ran
|
||||||
|
in the same order they were defined in
|
||||||
|
|
||||||
|
## Submitting your solution
|
||||||
|
|
||||||
|
You can submit your solution using the `exercism submit lib/acronym.ex` command.
|
||||||
|
This command will upload your solution to the Exercism website and print the solution page's URL.
|
||||||
|
|
||||||
|
It's possible to submit an incomplete solution which allows you to:
|
||||||
|
|
||||||
|
- See how others have completed the exercise
|
||||||
|
- Request help from a mentor
|
||||||
|
|
||||||
|
## Need to get help?
|
||||||
|
|
||||||
|
If you'd like help solving the exercise, check the following pages:
|
||||||
|
|
||||||
|
- The [Elixir track's documentation](https://exercism.org/docs/tracks/elixir)
|
||||||
|
- [Exercism's support channel on gitter](https://gitter.im/exercism/support)
|
||||||
|
- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs)
|
||||||
|
|
||||||
|
Should those resources not suffice, you could submit your (incomplete) solution to request mentoring.
|
||||||
|
|
||||||
|
If you're stuck on something, it may help to look at some of the [available resources](https://exercism.org/docs/tracks/elixir/resources) out there where answers might be found.
|
||||||
|
If you can't find what you're looking for in the documentation, feel free to ask help in the Exercism's BEAM [gitter channel](https://gitter.im/exercism/xerlang).
|
||||||
40
acronym/README.md
Normal file
40
acronym/README.md
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
# Acronym
|
||||||
|
|
||||||
|
Welcome to Acronym on Exercism's Elixir Track.
|
||||||
|
If you need help running the tests or submitting your code, check out `HELP.md`.
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
Convert a phrase to its acronym.
|
||||||
|
|
||||||
|
Techies love their TLA (Three Letter Acronyms)!
|
||||||
|
|
||||||
|
Help generate some jargon by writing a program that converts a long name
|
||||||
|
like Portable Network Graphics to its acronym (PNG).
|
||||||
|
|
||||||
|
## Source
|
||||||
|
|
||||||
|
### Created by
|
||||||
|
|
||||||
|
- @Teapane
|
||||||
|
|
||||||
|
### Contributed to by
|
||||||
|
|
||||||
|
- @angelikatyborska
|
||||||
|
- @Cohen-Carlisle
|
||||||
|
- @dalexj
|
||||||
|
- @devonestes
|
||||||
|
- @gmile
|
||||||
|
- @henrik
|
||||||
|
- @jwworth
|
||||||
|
- @lpil
|
||||||
|
- @martinsvalin
|
||||||
|
- @neenjaw
|
||||||
|
- @parkerl
|
||||||
|
- @rubysolo
|
||||||
|
- @sotojuan
|
||||||
|
- @waiting-for-dev
|
||||||
|
|
||||||
|
### Based on
|
||||||
|
|
||||||
|
Julien Vanier - https://github.com/monkbroc
|
||||||
12
acronym/lib/acronym.ex
Normal file
12
acronym/lib/acronym.ex
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
defmodule Acronym do
|
||||||
|
@doc """
|
||||||
|
Generate an acronym from a string.
|
||||||
|
"This is a string" => "TIAS"
|
||||||
|
"""
|
||||||
|
@spec abbreviate(String.t()) :: String.t()
|
||||||
|
def abbreviate(string) do
|
||||||
|
String.split(string, [" ", "-", "_"])
|
||||||
|
|> Enum.map_join(&String.at(&1, 0))
|
||||||
|
|> String.upcase()
|
||||||
|
end
|
||||||
|
end
|
||||||
28
acronym/mix.exs
Normal file
28
acronym/mix.exs
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
defmodule Acronym.MixProject do
|
||||||
|
use Mix.Project
|
||||||
|
|
||||||
|
def project do
|
||||||
|
[
|
||||||
|
app: :acronym,
|
||||||
|
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
|
||||||
41
acronym/test/acronym_test.exs
Normal file
41
acronym/test/acronym_test.exs
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
defmodule AcronymTest do
|
||||||
|
use ExUnit.Case
|
||||||
|
|
||||||
|
test "it produces acronyms from title case" do
|
||||||
|
assert Acronym.abbreviate("Portable Networks Graphic") === "PNG"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it produces acronyms from lower case" do
|
||||||
|
assert Acronym.abbreviate("Ruby on Rails") === "ROR"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it ignores punctuation" do
|
||||||
|
assert Acronym.abbreviate("First in, First out") === "FIFO"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it produces acronyms from phrases with acronyms" do
|
||||||
|
assert Acronym.abbreviate("GNU Image Manipulation Program") === "GIMP"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it produces acronyms ignoring punctuation and casing" do
|
||||||
|
assert Acronym.abbreviate("Complementary Metal-Oxide semiconductor") === "CMOS"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it produces a very long abbreviation" do
|
||||||
|
assert Acronym.abbreviate(
|
||||||
|
"Rolling On The Floor Laughing So Hard That My Dogs Came Over And Licked Me"
|
||||||
|
) === "ROTFLSHTMDCOALM"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it produces acronyms from phrases with consecutive delimiters" do
|
||||||
|
assert Acronym.abbreviate("Something - I made up from thin air") === "SIMUFTA"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it produces acronyms from phrases with apostrophes" do
|
||||||
|
assert Acronym.abbreviate("Halley's Comet") === "HC"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it produces acronyms from phrases with underscore emphasis" do
|
||||||
|
assert Acronym.abbreviate("The Road _Not_ Taken") === "TRNT"
|
||||||
|
end
|
||||||
|
end
|
||||||
2
acronym/test/test_helper.exs
Normal file
2
acronym/test/test_helper.exs
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
ExUnit.start()
|
||||||
|
ExUnit.configure(exclude: :pending, trace: true)
|
||||||
41
anagram/.exercism/config.json
Normal file
41
anagram/.exercism/config.json
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
{
|
||||||
|
"blurb": "Given a word and a list of possible anagrams, select the correct sublist.",
|
||||||
|
"authors": [
|
||||||
|
"rubysolo"
|
||||||
|
],
|
||||||
|
"contributors": [
|
||||||
|
"andrewsardone",
|
||||||
|
"angelikatyborska",
|
||||||
|
"Br1ght0ne",
|
||||||
|
"Cohen-Carlisle",
|
||||||
|
"crazymykl",
|
||||||
|
"dalexj",
|
||||||
|
"devonestes",
|
||||||
|
"henrik",
|
||||||
|
"jeremy-w",
|
||||||
|
"jinyeow",
|
||||||
|
"kytrinyx",
|
||||||
|
"lpil",
|
||||||
|
"markijbema",
|
||||||
|
"neenjaw",
|
||||||
|
"parkerl",
|
||||||
|
"pminten",
|
||||||
|
"sotojuan",
|
||||||
|
"Teapane",
|
||||||
|
"tjcelaya",
|
||||||
|
"waiting-for-dev"
|
||||||
|
],
|
||||||
|
"files": {
|
||||||
|
"solution": [
|
||||||
|
"lib/anagram.ex"
|
||||||
|
],
|
||||||
|
"test": [
|
||||||
|
"test/anagram_test.exs"
|
||||||
|
],
|
||||||
|
"example": [
|
||||||
|
".meta/example.ex"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"source": "Inspired by the Extreme Startup game",
|
||||||
|
"source_url": "https://github.com/rchatley/extreme_startup"
|
||||||
|
}
|
||||||
1
anagram/.exercism/metadata.json
Normal file
1
anagram/.exercism/metadata.json
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"track":"elixir","exercise":"anagram","id":"eb2a62e64232441fbaec55fd055f199e","url":"https://exercism.org/tracks/elixir/exercises/anagram","handle":"rjNemo","is_requester":true,"auto_approve":false}
|
||||||
4
anagram/.formatter.exs
Normal file
4
anagram/.formatter.exs
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
# Used by "mix format"
|
||||||
|
[
|
||||||
|
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
||||||
|
]
|
||||||
24
anagram/.gitignore
vendored
Normal file
24
anagram/.gitignore
vendored
Normal file
|
|
@ -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").
|
||||||
|
anagram-*.tar
|
||||||
|
|
||||||
75
anagram/HELP.md
Normal file
75
anagram/HELP.md
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
# Help
|
||||||
|
|
||||||
|
## Running the tests
|
||||||
|
|
||||||
|
From the terminal, change to the base directory of the exercise then execute the tests with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ mix test
|
||||||
|
```
|
||||||
|
|
||||||
|
This will execute the test file found in the `test` subfolder -- a file ending in `_test.exs`
|
||||||
|
|
||||||
|
Documentation:
|
||||||
|
|
||||||
|
* [`mix test` - Elixir's test execution tool](https://hexdocs.pm/mix/Mix.Tasks.Test.html)
|
||||||
|
* [`ExUnit` - Elixir's unit test library](https://hexdocs.pm/ex_unit/ExUnit.html)
|
||||||
|
|
||||||
|
## Pending tests
|
||||||
|
|
||||||
|
In test suites of practice exercises, all but the first test have been tagged to be 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
|
||||||
|
```
|
||||||
|
|
||||||
|
If you wish to run all tests at once, you can include all skipped test by using the `--include` flag on the `mix test` command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ mix test --include pending
|
||||||
|
```
|
||||||
|
|
||||||
|
Or, you can enable all the tests by commenting out the `ExUnit.configure` line in the file `test/test_helper.exs`.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
# ExUnit.configure(exclude: :pending, trace: true)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Useful `mix test` options
|
||||||
|
|
||||||
|
* `test/<FILE>.exs:LINENUM` - runs only a single test, the test from `<FILE>.exs` whose definition is on line `LINENUM`
|
||||||
|
* `--failed` - runs only tests that failed the last time they ran
|
||||||
|
* `--max-failures` - the suite stops evaluating tests when this number of test failures
|
||||||
|
is reached
|
||||||
|
* `--seed 0` - disables randomization so the tests in a single file will always be ran
|
||||||
|
in the same order they were defined in
|
||||||
|
|
||||||
|
## Submitting your solution
|
||||||
|
|
||||||
|
You can submit your solution using the `exercism submit lib/anagram.ex` command.
|
||||||
|
This command will upload your solution to the Exercism website and print the solution page's URL.
|
||||||
|
|
||||||
|
It's possible to submit an incomplete solution which allows you to:
|
||||||
|
|
||||||
|
- See how others have completed the exercise
|
||||||
|
- Request help from a mentor
|
||||||
|
|
||||||
|
## Need to get help?
|
||||||
|
|
||||||
|
If you'd like help solving the exercise, check the following pages:
|
||||||
|
|
||||||
|
- The [Elixir track's documentation](https://exercism.org/docs/tracks/elixir)
|
||||||
|
- [Exercism's support channel on gitter](https://gitter.im/exercism/support)
|
||||||
|
- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs)
|
||||||
|
|
||||||
|
Should those resources not suffice, you could submit your (incomplete) solution to request mentoring.
|
||||||
|
|
||||||
|
If you're stuck on something, it may help to look at some of the [available resources](https://exercism.org/docs/tracks/elixir/resources) out there where answers might be found.
|
||||||
|
If you can't find what you're looking for in the documentation, feel free to ask help in the Exercism's BEAM [gitter channel](https://gitter.im/exercism/xerlang).
|
||||||
51
anagram/README.md
Normal file
51
anagram/README.md
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
# Anagram
|
||||||
|
|
||||||
|
Welcome to Anagram on Exercism's Elixir Track.
|
||||||
|
If you need help running the tests or submitting your code, check out `HELP.md`.
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
An anagram is a rearrangement of letters to form a new word: for example `"owns"` is an anagram of `"snow"`.
|
||||||
|
A word is not its own anagram: for example, `"stop"` is not an anagram of `"stop"`.
|
||||||
|
|
||||||
|
Given a target word and a set of candidate words, this exercise requests the anagram set: the subset of the candidates that are anagrams of the target.
|
||||||
|
|
||||||
|
The target and candidates are words of one or more ASCII alphabetic characters (`A`-`Z` and `a`-`z`).
|
||||||
|
Lowercase and uppercase characters are equivalent: for example, `"PoTS"` is an anagram of `"sTOp"`, but `StoP` is not an anagram of `sTOp`.
|
||||||
|
The anagram set is the subset of the candidate set that are anagrams of the target (in any order).
|
||||||
|
Words in the anagram set should have the same letter case as in the candidate set.
|
||||||
|
|
||||||
|
Given the target `"stone"` and candidates `"stone"`, `"tones"`, `"banana"`, `"tons"`, `"notes"`, `"Seton"`, the anagram set is `"tones"`, `"notes"`, `"Seton"`.
|
||||||
|
|
||||||
|
## Source
|
||||||
|
|
||||||
|
### Created by
|
||||||
|
|
||||||
|
- @rubysolo
|
||||||
|
|
||||||
|
### Contributed to by
|
||||||
|
|
||||||
|
- @andrewsardone
|
||||||
|
- @angelikatyborska
|
||||||
|
- @Br1ght0ne
|
||||||
|
- @Cohen-Carlisle
|
||||||
|
- @crazymykl
|
||||||
|
- @dalexj
|
||||||
|
- @devonestes
|
||||||
|
- @henrik
|
||||||
|
- @jeremy-w
|
||||||
|
- @jinyeow
|
||||||
|
- @kytrinyx
|
||||||
|
- @lpil
|
||||||
|
- @markijbema
|
||||||
|
- @neenjaw
|
||||||
|
- @parkerl
|
||||||
|
- @pminten
|
||||||
|
- @sotojuan
|
||||||
|
- @Teapane
|
||||||
|
- @tjcelaya
|
||||||
|
- @waiting-for-dev
|
||||||
|
|
||||||
|
### Based on
|
||||||
|
|
||||||
|
Inspired by the Extreme Startup game - https://github.com/rchatley/extreme_startup
|
||||||
8
anagram/lib/anagram.ex
Normal file
8
anagram/lib/anagram.ex
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
defmodule Anagram do
|
||||||
|
@doc """
|
||||||
|
Returns all candidates that are anagrams of, but not equal to, 'base'.
|
||||||
|
"""
|
||||||
|
@spec match(String.t(), [String.t()]) :: [String.t()]
|
||||||
|
def match(base, candidates) do
|
||||||
|
end
|
||||||
|
end
|
||||||
28
anagram/mix.exs
Normal file
28
anagram/mix.exs
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
defmodule Anagram.MixProject do
|
||||||
|
use Mix.Project
|
||||||
|
|
||||||
|
def project do
|
||||||
|
[
|
||||||
|
app: :anagram,
|
||||||
|
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
|
||||||
87
anagram/test/anagram_test.exs
Normal file
87
anagram/test/anagram_test.exs
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
defmodule AnagramTest do
|
||||||
|
use ExUnit.Case
|
||||||
|
|
||||||
|
# @tag :pending
|
||||||
|
test "no matches" do
|
||||||
|
matches = Anagram.match("diaper", ~w(hello world zombies pants))
|
||||||
|
assert matches == []
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag :pending
|
||||||
|
test "detects two anagrams" do
|
||||||
|
matches = Anagram.match("solemn", ~w(lemons cherry melons))
|
||||||
|
assert matches == ~w(lemons melons)
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag :pending
|
||||||
|
test "does not detect anagram subsets" do
|
||||||
|
matches = Anagram.match("good", ~w(dog goody))
|
||||||
|
assert matches == []
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag :pending
|
||||||
|
test "detects anagram" do
|
||||||
|
matches = Anagram.match("listen", ~w(enlists google inlets banana))
|
||||||
|
assert matches == ~w(inlets)
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag :pending
|
||||||
|
test "detects three anagrams" do
|
||||||
|
matches = Anagram.match("allergy", ~w(gallery ballerina regally clergy largely leading))
|
||||||
|
assert matches == ~w(gallery regally largely)
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag :pending
|
||||||
|
test "detects multiple anagrams with different case" do
|
||||||
|
matches = Anagram.match("nose", ~w(Eons ONES))
|
||||||
|
assert matches == ~w(Eons ONES)
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag :pending
|
||||||
|
test "does not detect non-anagrams with identical checksum" do
|
||||||
|
matches = Anagram.match("mass", ~w(last))
|
||||||
|
assert matches == []
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag :pending
|
||||||
|
test "detect anagrams case-insensitively" do
|
||||||
|
matches = Anagram.match("orchestra", ~w(cashregister Carthorse radishes))
|
||||||
|
assert matches == ~w(Carthorse)
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag :pending
|
||||||
|
test "detects anagrams using case-insensitive subject" do
|
||||||
|
matches = Anagram.match("Orchestra", ~w(cashregister carthorse radishes))
|
||||||
|
assert matches == ~w(carthorse)
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag :pending
|
||||||
|
test "detects anagrams using case-insensitive possible matches" do
|
||||||
|
matches = Anagram.match("orchestra", ~w(cashregister Carthorse radishes))
|
||||||
|
assert matches == ~w(Carthorse)
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag :pending
|
||||||
|
test "does not detect an anagram if the original word is repeated" do
|
||||||
|
matches = Anagram.match("go", ~w(go Go GO))
|
||||||
|
assert matches == []
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag :pending
|
||||||
|
test "anagrams must use all letters exactly once" do
|
||||||
|
matches = Anagram.match("tapper", ~w(patter))
|
||||||
|
assert matches == []
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag :pending
|
||||||
|
test "words are not anagrams of themselves (case-insensitive)" do
|
||||||
|
matches = Anagram.match("BANANA", ~w(BANANA Banana banana))
|
||||||
|
assert matches == []
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag :pending
|
||||||
|
test "words other than themselves can be anagrams" do
|
||||||
|
matches = Anagram.match("LISTEN", ~w(Listen Silent LISTEN))
|
||||||
|
assert matches == ~w(Silent)
|
||||||
|
end
|
||||||
|
end
|
||||||
2
anagram/test/test_helper.exs
Normal file
2
anagram/test/test_helper.exs
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
ExUnit.start()
|
||||||
|
ExUnit.configure(exclude: :pending, trace: true)
|
||||||
19
top-secret/.exercism/config.json
Normal file
19
top-secret/.exercism/config.json
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"blurb": "Learn about the Abstract Syntax Tree (AST) by helping decode secret messages from Agent Ex.",
|
||||||
|
"authors": [
|
||||||
|
"jiegillet",
|
||||||
|
"angelikatyborska"
|
||||||
|
],
|
||||||
|
"files": {
|
||||||
|
"solution": [
|
||||||
|
"lib/top_secret.ex"
|
||||||
|
],
|
||||||
|
"test": [
|
||||||
|
"test/top_secret_test.exs"
|
||||||
|
],
|
||||||
|
"exemplar": [
|
||||||
|
".meta/exemplar.ex"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"language_versions": ">=1.10"
|
||||||
|
}
|
||||||
1
top-secret/.exercism/metadata.json
Normal file
1
top-secret/.exercism/metadata.json
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"track":"elixir","exercise":"top-secret","id":"5b9a04b7076a477189cf300bf55d6c31","url":"https://exercism.org/tracks/elixir/exercises/top-secret","handle":"rjNemo","is_requester":true,"auto_approve":false}
|
||||||
4
top-secret/.formatter.exs
Normal file
4
top-secret/.formatter.exs
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
# Used by "mix format"
|
||||||
|
[
|
||||||
|
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
||||||
|
]
|
||||||
24
top-secret/.gitignore
vendored
Normal file
24
top-secret/.gitignore
vendored
Normal file
|
|
@ -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").
|
||||||
|
maps-*.tar
|
||||||
|
|
||||||
75
top-secret/HELP.md
Normal file
75
top-secret/HELP.md
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
# Help
|
||||||
|
|
||||||
|
## Running the tests
|
||||||
|
|
||||||
|
From the terminal, change to the base directory of the exercise then execute the tests with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ mix test
|
||||||
|
```
|
||||||
|
|
||||||
|
This will execute the test file found in the `test` subfolder -- a file ending in `_test.exs`
|
||||||
|
|
||||||
|
Documentation:
|
||||||
|
|
||||||
|
* [`mix test` - Elixir's test execution tool](https://hexdocs.pm/mix/Mix.Tasks.Test.html)
|
||||||
|
* [`ExUnit` - Elixir's unit test library](https://hexdocs.pm/ex_unit/ExUnit.html)
|
||||||
|
|
||||||
|
## Pending tests
|
||||||
|
|
||||||
|
In test suites of practice exercises, all but the first test have been tagged to be 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
|
||||||
|
```
|
||||||
|
|
||||||
|
If you wish to run all tests at once, you can include all skipped test by using the `--include` flag on the `mix test` command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ mix test --include pending
|
||||||
|
```
|
||||||
|
|
||||||
|
Or, you can enable all the tests by commenting out the `ExUnit.configure` line in the file `test/test_helper.exs`.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
# ExUnit.configure(exclude: :pending, trace: true)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Useful `mix test` options
|
||||||
|
|
||||||
|
* `test/<FILE>.exs:LINENUM` - runs only a single test, the test from `<FILE>.exs` whose definition is on line `LINENUM`
|
||||||
|
* `--failed` - runs only tests that failed the last time they ran
|
||||||
|
* `--max-failures` - the suite stops evaluating tests when this number of test failures
|
||||||
|
is reached
|
||||||
|
* `--seed 0` - disables randomization so the tests in a single file will always be ran
|
||||||
|
in the same order they were defined in
|
||||||
|
|
||||||
|
## Submitting your solution
|
||||||
|
|
||||||
|
You can submit your solution using the `exercism submit lib/top_secret.ex` command.
|
||||||
|
This command will upload your solution to the Exercism website and print the solution page's URL.
|
||||||
|
|
||||||
|
It's possible to submit an incomplete solution which allows you to:
|
||||||
|
|
||||||
|
- See how others have completed the exercise
|
||||||
|
- Request help from a mentor
|
||||||
|
|
||||||
|
## Need to get help?
|
||||||
|
|
||||||
|
If you'd like help solving the exercise, check the following pages:
|
||||||
|
|
||||||
|
- The [Elixir track's documentation](https://exercism.org/docs/tracks/elixir)
|
||||||
|
- [Exercism's support channel on gitter](https://gitter.im/exercism/support)
|
||||||
|
- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs)
|
||||||
|
|
||||||
|
Should those resources not suffice, you could submit your (incomplete) solution to request mentoring.
|
||||||
|
|
||||||
|
If you're stuck on something, it may help to look at some of the [available resources](https://exercism.org/docs/tracks/elixir/resources) out there where answers might be found.
|
||||||
|
If you can't find what you're looking for in the documentation, feel free to ask help in the Exercism's BEAM [gitter channel](https://gitter.im/exercism/xerlang).
|
||||||
51
top-secret/HINTS.md
Normal file
51
top-secret/HINTS.md
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
# Hints
|
||||||
|
|
||||||
|
## General
|
||||||
|
|
||||||
|
- Read about quoting in the [official Getting Started guide][getting-started-quote].
|
||||||
|
- Read the [introduction to Elixir AST by Lucas San Román][ast-intro-lucas].
|
||||||
|
- Read [the official documentation for `quote`][doc-quote].
|
||||||
|
- Inspect the output of [`quote`][doc-quote] to familiarize yourself with how ASTs look like for specific code snippets.
|
||||||
|
|
||||||
|
## 1. Turn code into data
|
||||||
|
|
||||||
|
- There is a [built-in function][doc-code-string-to-quoted] that turns a string with code into an AST.
|
||||||
|
|
||||||
|
## 2. Parse a single AST node
|
||||||
|
|
||||||
|
- Inspect the output of [`quote`][doc-quote] to familiarize yourself with how ASTs look like for specific code snippets.
|
||||||
|
- The operations that define a function are `:def` and `:defp`.
|
||||||
|
- The operation is the first element in a three-element AST node tuple.
|
||||||
|
- You can ignore the second element in the tuple in this exercise completely.
|
||||||
|
- The third element in the tuple is the argument list of the operation that defines the function.
|
||||||
|
- The first element on that list is the function's name, and the second element is the function's body.
|
||||||
|
|
||||||
|
## 3. Decode the secret message part from function definition
|
||||||
|
|
||||||
|
- Inspect the output of [`quote`][doc-quote] to familiarize yourself with how ASTs look like for specific code snippets.
|
||||||
|
- The AST node that contains the function's name also contains the function's argument list as the third element.
|
||||||
|
- The arity of a function is the length of its argument list.
|
||||||
|
- There is a [built-in function in the `String` module][string-slice] that can get the first `n` characters from a string.
|
||||||
|
- A function without arguments written without parentheses will not have a list as argument but an atom.
|
||||||
|
|
||||||
|
## 4. Fix the decoding for functions with guards
|
||||||
|
|
||||||
|
- Inspect the output of [`quote`][doc-quote] to familiarize yourself with how ASTs look like for specific code snippets.
|
||||||
|
- When a function has a guard, the third element in the tuple for the `:def/:defp` operation is a bit different.
|
||||||
|
- That third element is a list with two elements, the first one is the tuple for the `:when` operation, and the second one is the function's body.
|
||||||
|
- The `:when` operation's arguments are a two-element list, where the first argument is the function's name, and the second is the guard expression.
|
||||||
|
|
||||||
|
## 5. Decode the full secret message
|
||||||
|
|
||||||
|
- Use the function `to_ast/1` that you implemented in the first task to create the AST.
|
||||||
|
- There is a [built-in function][macro-prewalk] that can visit each node in an AST with an accumulator.
|
||||||
|
- Use the function `decode_secret_message_part/2` that you implemented in previous tasks to prewalk the AST.
|
||||||
|
- To reverse the accumulator at the end and turn it into a string, refresh your knowledge of the [`Enum` module][enum].
|
||||||
|
|
||||||
|
[getting-started-quote]: https://elixir-lang.org/getting-started/meta/quote-and-unquote.html#quoting
|
||||||
|
[doc-quote]: https://hexdocs.pm/elixir/Kernel.SpecialForms.html#quote/2
|
||||||
|
[ast-intro-lucas]: https://dorgan.ar/posts/2021/04/the_elixir_ast/
|
||||||
|
[doc-code-string-to-quoted]: https://hexdocs.pm/elixir/Code.html#string_to_quoted/2
|
||||||
|
[string-slice]: https://hexdocs.pm/elixir/String.html#slice/2
|
||||||
|
[macro-prewalk]: https://hexdocs.pm/elixir/Macro.html#prewalk/3
|
||||||
|
[enum]: https://hexdocs.pm/elixir/Enum.html
|
||||||
137
top-secret/README.md
Normal file
137
top-secret/README.md
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
# Top Secret
|
||||||
|
|
||||||
|
Welcome to Top Secret on Exercism's Elixir Track.
|
||||||
|
If you need help running the tests or submitting your code, check out `HELP.md`.
|
||||||
|
If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :)
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
## AST
|
||||||
|
|
||||||
|
The Abstract Syntax Tree (AST), also called a _quoted expression_, is a way to represent code as data.
|
||||||
|
|
||||||
|
Each node in the AST is a three-element tuple.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
# AST representation of:
|
||||||
|
# 2 + 3
|
||||||
|
{:+, [], [2, 3]}
|
||||||
|
```
|
||||||
|
|
||||||
|
The first element, an atom, is the operation. The second element, a keyword list, is the metadata. The third element is a list of arguments, which contains other nodes. Literal values such as integers, atoms, and strings are represented in the AST as themselves instead of three-element tuples.
|
||||||
|
|
||||||
|
### Turning code into ASTs
|
||||||
|
|
||||||
|
Changing Elixir code to ASTs and ASTs back to code is part of the standard library. You can find functions for working with ASTs in the modules `Code` (e.g. to change a string with code to an AST) and `Macro` (e.g. to traverse the AST or change it to a string).
|
||||||
|
|
||||||
|
Note that all of the functions in the standard library use the name "quoted" to mean the AST (short for _quoted expression_).
|
||||||
|
|
||||||
|
The special form for turning code into an AST is called `quote`. It accepts a code block and returns its AST.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
quote do
|
||||||
|
2 + 3 - 1
|
||||||
|
end
|
||||||
|
|
||||||
|
# => {:-, [], [
|
||||||
|
# {:+, [], [2, 3]},
|
||||||
|
# 1
|
||||||
|
# ]}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Use cases
|
||||||
|
|
||||||
|
The ability to represent code as an AST is at the heart of metaprogramming in Elixir. _Macros_, which is a way to write Elixir code that produces Elixir code, work by returning ASTs as output.
|
||||||
|
|
||||||
|
Another use case for ASTs is static code analysis, like Exercism's own tool, the Analyzer, which you might already know as the little bot that leaves comments on your solutions.
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
You're part of a task force fighting against corporate espionage. You have a secret informer at Shady Company X, which you suspect of stealing secrets from its competitors.
|
||||||
|
|
||||||
|
Your informer, Agent Ex, is an Elixir developer. She is encoding secret messages in her code.
|
||||||
|
|
||||||
|
To decode her secret messages:
|
||||||
|
|
||||||
|
- Take all functions (public and private) in the order they're defined in.
|
||||||
|
- For each function, take the first `n` characters from its name, where `n` is the function's arity.
|
||||||
|
|
||||||
|
## 1. Turn code into data
|
||||||
|
|
||||||
|
Implement the `TopSecret.to_ast/1` function. It should take a string with Elixir code and return its AST.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
TopSecret.to_ast("div(4, 3)")
|
||||||
|
# => {:div, [line: 1], [4, 3]}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Parse a single AST node
|
||||||
|
|
||||||
|
Implement the `TopSecret.decode_secret_message_part/2` function. It should take an AST node and an accumulator for the secret message (a list). It should return a tuple with the AST node unchanged as the first element, and the accumulator as the second element.
|
||||||
|
|
||||||
|
If the operation of the AST node is defining a function (`def` or `defp`), prepend the function name (changed to a string) to the accumulator. If the operation is something else, return the accumulator unchanged.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
ast_node = TopSecret.to_ast("defp cat(a, b, c), do: nil")
|
||||||
|
TopSecret.decode_secret_message_part(ast_node, ["day"])
|
||||||
|
# => {ast_node, ["cat", "day"]}
|
||||||
|
|
||||||
|
ast_node = TopSecret.to_ast("10 + 3")
|
||||||
|
TopSecret.decode_secret_message_part(ast_node, ["day"])
|
||||||
|
# => {ast_node, ["day"]}
|
||||||
|
```
|
||||||
|
|
||||||
|
This function doesn't need to do any recursive calls to check the whole AST, only the given node. We will traverse the whole AST with built-in tools in the last step.
|
||||||
|
|
||||||
|
## 3. Decode the secret message part from function definition
|
||||||
|
|
||||||
|
Extend the `TopSecret.decode_secret_message_part/2` function. If the operation in the AST node is defining a function, don't return the whole function name. Instead, check the function's arity. Then, return only first `n` character from the name, where `n` is the arity.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
ast_node = TopSecret.to_ast("defp cat(a, b), do: nil")
|
||||||
|
TopSecret.decode_secret_message_part(ast_node, ["day"])
|
||||||
|
# => {ast_node, ["ca", "day"]}
|
||||||
|
|
||||||
|
ast_node = TopSecret.to_ast("defp cat(), do: nil")
|
||||||
|
TopSecret.decode_secret_message_part(ast_node, ["day"])
|
||||||
|
# => {ast_node, ["", "day"]}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Fix the decoding for functions with guards
|
||||||
|
|
||||||
|
Extend the `TopSecret.decode_secret_message_part/2` function. Make sure the function's name and arity is correctly detected for function definitions that use guards.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
ast_node = TopSecret.to_ast("defp cat(a, b) when is_nil(a), do: nil")
|
||||||
|
TopSecret.decode_secret_message_part(ast_node, ["day"])
|
||||||
|
# => {ast_node, ["ca", "day"]}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Decode the full secret message
|
||||||
|
|
||||||
|
Implement the `TopSecret.decode_secret_message/1` function. It should take a string with Elixir code and return the secret message as a string decoded from all function definitions found in the code. Make sure to reuse functions defined in previous steps.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
code = """
|
||||||
|
defmodule MyCalendar do
|
||||||
|
def busy?(date, time) do
|
||||||
|
Date.day_of_week(date) != 7 and
|
||||||
|
time.hour in 10..16
|
||||||
|
end
|
||||||
|
|
||||||
|
def yesterday?(date) do
|
||||||
|
Date.diff(Date.utc_today, date)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
"""
|
||||||
|
|
||||||
|
TopSecret.decode_secret_message(code)
|
||||||
|
# => "buy"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Source
|
||||||
|
|
||||||
|
### Created by
|
||||||
|
|
||||||
|
- @jiegillet
|
||||||
|
- @angelikatyborska
|
||||||
26
top-secret/lib/top_secret.ex
Normal file
26
top-secret/lib/top_secret.ex
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
defmodule TopSecret do
|
||||||
|
def to_ast(string), do: Code.string_to_quoted!(string)
|
||||||
|
|
||||||
|
def decode_secret_message_part(ast, acc) do
|
||||||
|
case ast do
|
||||||
|
{:def, _, [{_, _, nil} | _]} ->
|
||||||
|
{ast, [""] ++ acc}
|
||||||
|
|
||||||
|
{:def, _, [{name, _, args} | _]} ->
|
||||||
|
{ast, [String.slice(to_string(name), 0, length(args))] ++ acc}
|
||||||
|
|
||||||
|
{:defp, _, [{_, _, nil} | _]} ->
|
||||||
|
{ast, [""] ++ acc}
|
||||||
|
|
||||||
|
{:defp, _, [{name, _, args} | _]} ->
|
||||||
|
{ast, [String.slice(to_string(name), 0, length(args))] ++ acc}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{ast, acc}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def decode_secret_message(string) do
|
||||||
|
# Please implement the decode_secret_message/1 function
|
||||||
|
end
|
||||||
|
end
|
||||||
28
top-secret/mix.exs
Normal file
28
top-secret/mix.exs
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
defmodule TopSecret.MixProject do
|
||||||
|
use Mix.Project
|
||||||
|
|
||||||
|
def project do
|
||||||
|
[
|
||||||
|
app: :top_secret,
|
||||||
|
version: "0.1.0",
|
||||||
|
# elixir: "~> 1.10",
|
||||||
|
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
|
||||||
2
top-secret/test/test_helper.exs
Normal file
2
top-secret/test/test_helper.exs
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
ExUnit.start()
|
||||||
|
ExUnit.configure(exclude: :pending, trace: true, seed: 0)
|
||||||
280
top-secret/test/top_secret_test.exs
Normal file
280
top-secret/test/top_secret_test.exs
Normal file
|
|
@ -0,0 +1,280 @@
|
||||||
|
defmodule TopSecretTest do
|
||||||
|
use ExUnit.Case
|
||||||
|
|
||||||
|
describe "to_ast/1" do
|
||||||
|
@tag task_id: 1
|
||||||
|
test "handles an empty string" do
|
||||||
|
string = ""
|
||||||
|
ast = {:__block__, [], []}
|
||||||
|
|
||||||
|
assert TopSecret.to_ast(string) == ast
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 1
|
||||||
|
test "handles a small code snippet" do
|
||||||
|
string = """
|
||||||
|
x = 7
|
||||||
|
y = x - 2
|
||||||
|
"""
|
||||||
|
|
||||||
|
ast =
|
||||||
|
{:__block__, [],
|
||||||
|
[
|
||||||
|
{:=, [line: 1], [{:x, [line: 1], nil}, 7]},
|
||||||
|
{:=, [line: 2], [{:y, [line: 2], nil}, {:-, [line: 2], [{:x, [line: 2], nil}, 2]}]}
|
||||||
|
]}
|
||||||
|
|
||||||
|
assert TopSecret.to_ast(string) == ast
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 1
|
||||||
|
test "handles a bigger code snippet" do
|
||||||
|
string = """
|
||||||
|
defmodule List do
|
||||||
|
@spec delete([], any) :: []
|
||||||
|
@spec delete([...], any) :: list
|
||||||
|
def delete(list, element)
|
||||||
|
end
|
||||||
|
"""
|
||||||
|
|
||||||
|
ast = {
|
||||||
|
:defmodule,
|
||||||
|
[line: 1],
|
||||||
|
[
|
||||||
|
{:__aliases__, [line: 1], [:List]},
|
||||||
|
[
|
||||||
|
do: {
|
||||||
|
:__block__,
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
{:@, [line: 2],
|
||||||
|
[
|
||||||
|
{:spec, [line: 2],
|
||||||
|
[{:"::", [line: 2], [{:delete, [line: 2], [[], {:any, [line: 2], nil}]}, []]}]}
|
||||||
|
]},
|
||||||
|
{:@, [line: 3],
|
||||||
|
[
|
||||||
|
{:spec, [line: 3],
|
||||||
|
[
|
||||||
|
{:"::", [line: 3],
|
||||||
|
[
|
||||||
|
{:delete, [line: 3], [[{:..., [line: 3], nil}], {:any, [line: 3], nil}]},
|
||||||
|
{:list, [line: 3], nil}
|
||||||
|
]}
|
||||||
|
]}
|
||||||
|
]},
|
||||||
|
{:def, [line: 4],
|
||||||
|
[{:delete, [line: 4], [{:list, [line: 4], nil}, {:element, [line: 4], nil}]}]}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert TopSecret.to_ast(string) == ast
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "decode_secret_message_part/2" do
|
||||||
|
@tag task_id: 2
|
||||||
|
test "returns the AST and accumulator unchanged" do
|
||||||
|
string = "2 + 3"
|
||||||
|
ast = TopSecret.to_ast(string)
|
||||||
|
acc = ["le", "mo"]
|
||||||
|
|
||||||
|
{actual_ast, actual_acc} = TopSecret.decode_secret_message_part(ast, acc)
|
||||||
|
assert actual_ast == ast
|
||||||
|
assert actual_acc == acc
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 2
|
||||||
|
test "appends a public function name to the accumulator" do
|
||||||
|
string = "def fit(a, b, c), do: :scale"
|
||||||
|
ast = TopSecret.to_ast(string)
|
||||||
|
acc = ["at"]
|
||||||
|
|
||||||
|
{actual_ast, actual_acc} = TopSecret.decode_secret_message_part(ast, acc)
|
||||||
|
assert actual_ast == ast
|
||||||
|
assert actual_acc == ["fit", "at"]
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 2
|
||||||
|
test "appends a private function name to the accumulator" do
|
||||||
|
string = "defp op(a, b), do: 2"
|
||||||
|
ast = TopSecret.to_ast(string)
|
||||||
|
acc = ["e", "ced"]
|
||||||
|
|
||||||
|
{actual_ast, actual_acc} = TopSecret.decode_secret_message_part(ast, acc)
|
||||||
|
assert actual_ast == ast
|
||||||
|
assert actual_acc == ["op", "e", "ced"]
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 2
|
||||||
|
test "ignores not top-level function definition" do
|
||||||
|
string = """
|
||||||
|
defmodule Math do
|
||||||
|
def sin(x), do: do_sin(x)
|
||||||
|
defp do_sin(x), do: nil
|
||||||
|
end
|
||||||
|
"""
|
||||||
|
|
||||||
|
ast = TopSecret.to_ast(string)
|
||||||
|
acc = []
|
||||||
|
|
||||||
|
{actual_ast, actual_acc} = TopSecret.decode_secret_message_part(ast, acc)
|
||||||
|
assert actual_ast == ast
|
||||||
|
assert actual_acc == acc
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 3
|
||||||
|
test "function arity affects message part length" do
|
||||||
|
string = "def adjust(a, b), do: :scale"
|
||||||
|
ast = TopSecret.to_ast(string)
|
||||||
|
acc = ["re"]
|
||||||
|
|
||||||
|
{actual_ast, actual_acc} = TopSecret.decode_secret_message_part(ast, acc)
|
||||||
|
assert actual_ast == ast
|
||||||
|
assert actual_acc == ["ad", "re"]
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 3
|
||||||
|
test "function arity 0 results in empty string" do
|
||||||
|
string = "def adjust(), do: :scale"
|
||||||
|
ast = TopSecret.to_ast(string)
|
||||||
|
acc = ["re"]
|
||||||
|
|
||||||
|
{actual_ast, actual_acc} = TopSecret.decode_secret_message_part(ast, acc)
|
||||||
|
assert actual_ast == ast
|
||||||
|
assert actual_acc == ["", "re"]
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 3
|
||||||
|
test "function arity 0 and no parentheses results in empty string" do
|
||||||
|
string = "def adjust, do: :scale"
|
||||||
|
ast = TopSecret.to_ast(string)
|
||||||
|
acc = ["re"]
|
||||||
|
|
||||||
|
{actual_ast, actual_acc} = TopSecret.decode_secret_message_part(ast, acc)
|
||||||
|
assert actual_ast == ast
|
||||||
|
assert actual_acc == ["", "re"]
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 4
|
||||||
|
test "works for public functions with a guard" do
|
||||||
|
string = "def sign(a) when a >= 0, do: :+"
|
||||||
|
ast = TopSecret.to_ast(string)
|
||||||
|
acc = ["e"]
|
||||||
|
|
||||||
|
{actual_ast, actual_acc} = TopSecret.decode_secret_message_part(ast, acc)
|
||||||
|
assert actual_ast == ast
|
||||||
|
assert actual_acc == ["s", "e"]
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 4
|
||||||
|
test "works for private functions with a guard" do
|
||||||
|
string = "defp do_sign(a) when a < 0, do: :-"
|
||||||
|
ast = TopSecret.to_ast(string)
|
||||||
|
acc = ["e"]
|
||||||
|
|
||||||
|
{actual_ast, actual_acc} = TopSecret.decode_secret_message_part(ast, acc)
|
||||||
|
assert actual_ast == ast
|
||||||
|
assert actual_acc == ["d", "e"]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "decode_secret_message/1" do
|
||||||
|
@tag task_id: 5
|
||||||
|
test "decodes a secret message from a single function definition" do
|
||||||
|
code = """
|
||||||
|
defmodule Notebook do
|
||||||
|
def note(notebook, text) do
|
||||||
|
add_to_notebook(notebook, text, append: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
"""
|
||||||
|
|
||||||
|
secret_message = "no"
|
||||||
|
|
||||||
|
assert TopSecret.decode_secret_message(code) == secret_message
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 5
|
||||||
|
test "decodes a secret message from a two function definitions" do
|
||||||
|
code = """
|
||||||
|
defmodule MyCalendar do
|
||||||
|
def busy?(date, time) do
|
||||||
|
Date.day_of_week(date) != 7 and
|
||||||
|
time.hour in 10..16
|
||||||
|
end
|
||||||
|
|
||||||
|
def yesterday?(date) do
|
||||||
|
Date.diff(Date.utc_today, date)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
"""
|
||||||
|
|
||||||
|
secret_message = "buy"
|
||||||
|
|
||||||
|
assert TopSecret.decode_secret_message(code) == secret_message
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 5
|
||||||
|
test "decodes a secret message from many function definitions" do
|
||||||
|
code = """
|
||||||
|
defmodule TotallyNotTopSecret do
|
||||||
|
def force(mass, acceleration), do: mass * acceleration
|
||||||
|
def uniform(from, to), do: rand.uniform(to - from) + from
|
||||||
|
def data(%{metadata: metadata}, _opts), do: model(metadata)
|
||||||
|
defp model(metadata, _opts), do: metadata |> less_data |> Enum.reverse() |> Enum.take(3)
|
||||||
|
defp less_data(data, _opts), do: Enum.reject(data, &is_nil/1)
|
||||||
|
end
|
||||||
|
"""
|
||||||
|
|
||||||
|
secret_message = "foundamole"
|
||||||
|
|
||||||
|
assert TopSecret.decode_secret_message(code) == secret_message
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 5
|
||||||
|
test "decodes a secret message without a module definition" do
|
||||||
|
code = """
|
||||||
|
def force(mass, acceleration), do: mass * acceleration
|
||||||
|
def uniform(from, to), do: rand.uniform(to - from) + from
|
||||||
|
def data(%{metadata: metadata}, _opts), do: model(metadata)
|
||||||
|
defp model(metadata, _opts), do: metadata |> less_data |> Enum.reverse() |> Enum.take(3)
|
||||||
|
defp less_data(data, _opts), do: Enum.reject(data, &is_nil/1)
|
||||||
|
"""
|
||||||
|
|
||||||
|
secret_message = "foundamole"
|
||||||
|
|
||||||
|
assert TopSecret.decode_secret_message(code) == secret_message
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 5
|
||||||
|
test "decodes another secret message from multiple modules" do
|
||||||
|
code = """
|
||||||
|
defmodule IOHelpers do
|
||||||
|
def inspect(x, opts), do: IO.inspect(x, opts)
|
||||||
|
def vi_or_vim(_env, _preference), do: :vim
|
||||||
|
def signal(pid, string), do: send(pid, {:signal, string})
|
||||||
|
def black(text, label), do: IO.ANSI.black <> label <> text <> IO.ANSI.reset()
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule TimeHelpers do
|
||||||
|
defp est_to_cet(time), do: Time.add(time, 6 * 60 * 60)
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule ASTHelpers do
|
||||||
|
def submodule?(m, _f, _args), do: String.contains?(m, ".")
|
||||||
|
def module({m, _f, _args}), do: m
|
||||||
|
def arity(_m, _f, args), do: length(args)
|
||||||
|
defp nested?(x, y) when is_list(y), do: x in y
|
||||||
|
end
|
||||||
|
"""
|
||||||
|
|
||||||
|
secret_message = "invisiblesubmarine"
|
||||||
|
|
||||||
|
assert TopSecret.decode_secret_message(code) == secret_message
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Reference in a new issue