85 lines
2.7 KiB
Elixir
85 lines
2.7 KiB
Elixir
|
defmodule SimpleCipher do
|
||
|
@chars ?a..?z
|
||
|
|
||
|
defguardp is_char(char) when char in @chars
|
||
|
defguardp is_sign(sign) when sign in [-1, 1]
|
||
|
|
||
|
defp cipher(text, key) do
|
||
|
length = String.length(text)
|
||
|
times = div(length, String.length(key)) + 1
|
||
|
|
||
|
key
|
||
|
|> Kernel.to_charlist()
|
||
|
|> Enum.map(&(&1 - ?a))
|
||
|
|> List.duplicate(times)
|
||
|
|> List.flatten()
|
||
|
|> Enum.take(length)
|
||
|
end
|
||
|
|
||
|
defp rotate(text, key, sign) when is_binary(text) and is_sign(sign) do
|
||
|
cipher = cipher(text, key)
|
||
|
|
||
|
text
|
||
|
|> Kernel.to_charlist()
|
||
|
|> Enum.with_index(fn element, index -> rotate(element, Enum.at(cipher, index), sign) end)
|
||
|
|> Kernel.to_string()
|
||
|
end
|
||
|
|
||
|
defp rotate(char, shift, sign) when is_sign(sign) and is_char(char) and is_char(char + sign * shift), do: char + sign * shift
|
||
|
defp rotate(char, shift, sign) when is_sign(sign) and is_char(char), do: char + sign * shift - sign * 26
|
||
|
|
||
|
@doc """
|
||
|
Given a `plaintext` and `key`, encode each character of the `plaintext` by
|
||
|
shifting it by the corresponding letter in the alphabet shifted by the number
|
||
|
of letters represented by the `key` character, repeating the `key` if it is
|
||
|
shorter than the `plaintext`.
|
||
|
|
||
|
For example, for the letter 'd', the alphabet is rotated to become:
|
||
|
|
||
|
defghijklmnopqrstuvwxyzabc
|
||
|
|
||
|
You would encode the `plaintext` by taking the current letter and mapping it
|
||
|
to the letter in the same position in this rotated alphabet.
|
||
|
|
||
|
abcdefghijklmnopqrstuvwxyz
|
||
|
defghijklmnopqrstuvwxyzabc
|
||
|
|
||
|
"a" becomes "d", "t" becomes "w", etc...
|
||
|
|
||
|
Each letter in the `plaintext` will be encoded with the alphabet of the `key`
|
||
|
character in the same position. If the `key` is shorter than the `plaintext`,
|
||
|
repeat the `key`.
|
||
|
|
||
|
Example:
|
||
|
|
||
|
plaintext = "testing"
|
||
|
key = "abc"
|
||
|
|
||
|
The key should repeat to become the same length as the text, becoming
|
||
|
"abcabca". If the key is longer than the text, only use as many letters of it
|
||
|
as are necessary.
|
||
|
"""
|
||
|
def encode(plaintext, key), do: rotate(plaintext, key, 1)
|
||
|
|
||
|
@doc """
|
||
|
Given a `ciphertext` and `key`, decode each character of the `ciphertext` by
|
||
|
finding the corresponding letter in the alphabet shifted by the number of
|
||
|
letters represented by the `key` character, repeating the `key` if it is
|
||
|
shorter than the `ciphertext`.
|
||
|
|
||
|
The same rules for key length and shifted alphabets apply as in `encode/2`,
|
||
|
but you will go the opposite way, so "d" becomes "a", "w" becomes "t",
|
||
|
etc..., depending on how much you shift the alphabet.
|
||
|
"""
|
||
|
def decode(ciphertext, key), do: rotate(ciphertext, key, -1)
|
||
|
|
||
|
@doc """
|
||
|
Generate a random key of a given length. It should contain lowercase letters only.
|
||
|
"""
|
||
|
def generate_key(length) do
|
||
|
1..length
|
||
|
|> Enum.map(fn _i -> Enum.random(@chars) end)
|
||
|
|> to_string()
|
||
|
end
|
||
|
end
|