defmodule RotationalCipher do @moduledoc """ Generates a simple Caesar cipher by rotating the alphabet by a given amount. Use for that shifted alphabet generation function and transliteration table based on two alphabets. """ @alphabet ?a..?z |> Enum.to_list() |> to_string() |> String.split("", trim: true) # Generates a shifted alphabet based on the given shift amount. # If swaped is true, the function will return a map with the index as the key and the character as the value, # otherwise the function will return a map with the character as the key and the index as the value. defp alphabet(shift \\ 0, swaped \\ false) when shift < 26 and shift >= 0 do @alphabet |> Enum.with_index(fn char, index -> position = if index + shift >= 26, do: index + shift - 26, else: index + shift if swaped == true, do: {position, char}, else: {char, position} end) |> Enum.into(%{}) end # Generates a transliteration table based on the given shift amount. defp transliterate_table(shift) do transliterated = alphabet(shift, true) alphabet() |> Enum.reduce(%{}, fn {char, index}, acc -> Map.merge(acc, %{ transliterated[index] => char, String.upcase(transliterated[index]) => String.upcase(char) }) end) end defp rotate_char(char, table), do: table[char] || char @doc """ Given a plaintext and amount to shift by, return a rotated string. Example: iex> RotationalCipher.rotate("Attack at dawn", 13) "Nggnpx ng qnja" """ @spec rotate(text :: String.t(), shift :: integer) :: String.t() def rotate(text, shift) when shift == 0 or shift == 26, do: text def rotate(text, shift) do table = transliterate_table(shift) text |> String.graphemes() |> Enum.map(&rotate_char(&1, table)) |> Enum.join() end end