Skip to content

Commit

Permalink
rotational-cipher
Browse files Browse the repository at this point in the history
  • Loading branch information
naomijub committed Jul 5, 2020
1 parent 3d86b76 commit db7d5ae
Show file tree
Hide file tree
Showing 9 changed files with 241 additions and 2 deletions.
3 changes: 1 addition & 2 deletions roman-numerals/lib/roman_numerals.ex
Expand Up @@ -52,8 +52,7 @@ defmodule RomanNumerals do
def numeral(number) do
number
|> Integer.to_string
|> String.split("")
|> Enum.reject(fn n -> n == "" end)
|> String.graphemes
|> Enum.reverse
|> Enum.with_index
|> Enum.map(&(val(&1)))
Expand Down
1 change: 1 addition & 0 deletions rotational-cipher/.exercism/metadata.json
@@ -0,0 +1 @@
{"track":"elixir","exercise":"rotational-cipher","id":"138e013b55404c058e2011d383b14c1d","url":"https://exercism.io/my/solutions/138e013b55404c058e2011d383b14c1d","handle":"jnaomi","is_requester":true,"auto_approve":false}
4 changes: 4 additions & 0 deletions rotational-cipher/.formatter.exs
@@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]
24 changes: 24 additions & 0 deletions rotational-cipher/.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").
rotational_cipher-*.tar

73 changes: 73 additions & 0 deletions rotational-cipher/README.md
@@ -0,0 +1,73 @@
# Rotational Cipher

Create an implementation of the rotational cipher, also sometimes called the Caesar cipher.

The Caesar cipher is a simple shift cipher that relies on
transposing all the letters in the alphabet using an integer key
between `0` and `26`. Using a key of `0` or `26` will always yield
the same output due to modular arithmetic. The letter is shifted
for as many values as the value of the key.

The general notation for rotational ciphers is `ROT + <key>`.
The most commonly used rotational cipher is `ROT13`.

A `ROT13` on the Latin alphabet would be as follows:

```text
Plain: abcdefghijklmnopqrstuvwxyz
Cipher: nopqrstuvwxyzabcdefghijklm
```

It is stronger than the Atbash cipher because it has 27 possible keys, and 25 usable keys.

Ciphertext is written out in the same formatting as the input including spaces and punctuation.

## Examples

- ROT5 `omg` gives `trl`
- ROT0 `c` gives `c`
- ROT26 `Cool` gives `Cool`
- ROT13 `The quick brown fox jumps over the lazy dog.` gives `Gur dhvpx oebja sbk whzcf bire gur ynml qbt.`
- ROT13 `Gur dhvpx oebja sbk whzcf bire gur ynml qbt.` gives `The quick brown fox jumps over the lazy dog.`

## 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

Wikipedia [https://en.wikipedia.org/wiki/Caesar_cipher](https://en.wikipedia.org/wiki/Caesar_cipher)

## Submitting Incomplete Solutions
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
33 changes: 33 additions & 0 deletions rotational-cipher/lib/rotational_cipher.ex
@@ -0,0 +1,33 @@
defmodule RotationalCipher do
@doc """
Given a plaintext and amount to shift by, return a rotated string.
Example:
iex> RotationalCipher.rotate("Attack at dawn", 13)
"Nggnpx ng qnja"
"""
@letters "abcdefghijklmnopqrstuvwxyz"
@capital "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

@spec rotate(text :: String.t(), shift :: integer) :: String.t()
def rotate(text, shift) do
text
|> String.graphemes
|> Enum.map(fn x ->
cond do
String.contains?(@letters, x) -> rotate_element(@letters, x, shift)
String.contains?(@capital, x) -> rotate_element(@capital, x, shift)
true -> x
end
end)
|> Enum.join

end

def rotate_element(alphabet, element, shift) do
{el_idx, _} = :binary.match(alphabet, element)
new_idx = rem(el_idx + shift, alphabet |> String.length)

String.at(alphabet, new_idx)
end
end
28 changes: 28 additions & 0 deletions rotational-cipher/mix.exs
@@ -0,0 +1,28 @@
defmodule RotationalCipher.MixProject do
use Mix.Project

def project do
[
app: :rotational_cipher,
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
75 changes: 75 additions & 0 deletions rotational-cipher/test/rotational_cipher_test.exs
@@ -0,0 +1,75 @@
defmodule RotationalCipherTest do
use ExUnit.Case

# @tag :pending
test "rotate a by 1" do
plaintext = "a"
shift = 1
assert RotationalCipher.rotate(plaintext, shift) == "b"
end

# @tag :pending
test "rotate a by 26, same output as input" do
plaintext = "a"
shift = 26
assert RotationalCipher.rotate(plaintext, shift) == "a"
end

# @tag :pending
test "rotate a by 0, same output as input" do
plaintext = "a"
shift = 0
assert RotationalCipher.rotate(plaintext, shift) == "a"
end

# @tag :pending
test "rotate m by 13" do
plaintext = "m"
shift = 13
assert RotationalCipher.rotate(plaintext, shift) == "z"
end

# @tag :pending
test "rotate n by 13 with wrap around alphabet" do
plaintext = "n"
shift = 13
assert RotationalCipher.rotate(plaintext, shift) == "a"
end

# @tag :pending
test "rotate capital letters" do
plaintext = "OMG"
shift = 5
assert RotationalCipher.rotate(plaintext, shift) == "TRL"
end

# @tag :pending
test "rotate spaces" do
plaintext = "O M G"
shift = 5
assert RotationalCipher.rotate(plaintext, shift) == "T R L"
end

# @tag :pending
test "rotate numbers" do
plaintext = "Testing 1 2 3 testing"
shift = 4
assert RotationalCipher.rotate(plaintext, shift) == "Xiwxmrk 1 2 3 xiwxmrk"
end

# @tag :pending
test "rotate punctuation" do
plaintext = "Let's eat, Grandma!"
shift = 21
assert RotationalCipher.rotate(plaintext, shift) == "Gzo'n zvo, Bmviyhv!"
end

# @tag :pending
test "rotate all letters" do
plaintext = "The quick brown fox jumps over the lazy dog."
shift = 13

assert RotationalCipher.rotate(plaintext, shift) ==
"Gur dhvpx oebja sbk whzcf bire gur ynml qbt."
end
end
2 changes: 2 additions & 0 deletions rotational-cipher/test/test_helper.exs
@@ -0,0 +1,2 @@
ExUnit.start()
ExUnit.configure(exclude: :pending, trace: true)

0 comments on commit db7d5ae

Please sign in to comment.