small medium large xlarge

• A basic Caesar cypher consists of shifting the letters is a message by a fixed offset. For an offset of 1, for example, a will become b, b will become c, and z will become a. If the offset is 13, we have the ROT13 algorithm.

Lists and binaries can both be string-like. Write a `Caesar` protocol that applies to both. It would include two functions: `encrypt(string, shift)` and `rot13(string)`.

My solution:

``````defprotocol Caesar do
def encrypt(string, shift)

def rot13(string)
end

defimpl Caesar, for: [BitString, List] do
@letter_length 26
@upper_case_start 65
@upper_case_end 90
@lower_case_start 97
@lower_case_end 122

defp is_upper_case?(i) do
i >= @upper_case_start && i <= @upper_case_end
end

defp is_lower_case?(i) do
i >= @lower_case_start && i <= @lower_case_end
end

def encrypt(string, shift) do
string
|> to_char_list
|> Enum.map(fn i -> shift_char(i, shift) end)
|> List.to_string
end

defp shift_char(i, shift) do
cond do
is_upper_case?(i) ->
rotate( i - @upper_case_start, shift) + @upper_case_start
is_lower_case?(i) ->
rotate(i - @lower_case_start, shift) + @lower_case_start
true ->
i
end
end

defp rotate(i, shift) do
rem(i + shift, @letter_length)
end

def rot13(string) do
encrypt(string, 13)
end
end
``````

My Solution:

``````defmodule Caesar.Shared do
def rot13(string) do
Caesar.encrypt(string, 13)
end
end

defprotocol Caesar do
def encrypt(string, shift)
defdelegate rot13(string), to: Caesar.Shared
end

defimpl Caesar, for: BitString do
def encrypt(string, shift) do
to_char_list(string)
|> Caesar.encrypt(shift)
|> to_string
end
end

defimpl Caesar, for: List do
def encrypt(string, shift) do
Enum.map(string, &rotate(&1, shift))
end

@upper 65..90
@lower 97..122

def rotate(char, shift) when char in @upper do
do_rotate(char, shift, @upper)
end

def rotate(char, shift) when char in @lower do
do_rotate(char, shift, @lower)
end

def rotate(char, _shift), do: char

defp do_rotate(char, shift, range) do
char
|> normalize(range)
|> shift(shift, range)
|> denormalize(range)
end

defp shift(char, shift, min..max) do
rem(char + shift, max-min+1)
end

defp normalize(char, min.._max) do
char - min
end

defp denormalize(char, min.._max) do
char + min
end
end

IO.puts Caesar.encrypt 'single quoted a-z and A-Z', 0
IO.puts Caesar.encrypt 'single quoted a-z and A-Z', 1
IO.puts Caesar.rot13 'single quoted a-z and A-Z'
IO.puts Caesar.rot13 "double quoted hello"
IO.puts Caesar.rot13 "Let's keep non-ASCII characters unencrypted to be VIM compatible, shall we?"
IO.puts Caesar.rot13 Caesar.rot13 "I am a SYMMETRIC cipher"
# single quoted a-z and A-Z
# tjohmf rvpufe b-a boe B-A
# fvatyr dhbgrq n-m naq N-M
# qbhoyr dhbgrq uryyb
# Yrg'f xrrc aba-NFPVV punenpgref harapelcgrq gb or IVZ pbzcngvoyr, funyy jr?
# I am a SYMMETRIC cipher
``````

Here’s my attempt. BTW, I tried to use the `when char in @upper` guard clause a la Patrick, but received an error about not being able to invoke remote function `Module.get_attribute/3` inside of a match.

``````defprotocol Cypher do
def encrypt(string, shift)
def rot13(string)
end

defimpl Cypher, for: List do

def encrypt(list, shift) do
list |> (Enum.map &encrypt_char(&1, shift))
end

def rot13(list), do: encrypt(list, 13)

def encrypt_char(char, shift) when char in ?a..?z do
?a + rem(char + shift - ?a, 26)
end
def encrypt_char(char, shift) when char in ?A..?Z do
?A + rem(char + shift - ?A, 26)
end
def encrypt_char(char = ?\s, shift), do: char

end

defimpl Cypher, for: BitString do
def encrypt(string, shift) do
Cypher.List.encrypt(String.to_char_list(string), shift)
|> List.to_string
end

def rot13(string), do: encrypt(string, 13)
end
``````
``````defprotocol Caesar do
def cypher(string, offset)
def rot13(string)
end

defimpl Caesar, for: [BitString] do
def cypher(string, offset), do: string
|> to_char_list
|> CaesarHelper.rotate(offset)

def rot13(string), do: cypher(string, 13)
end

defimpl Caesar, for: [List] do
def cypher(list, offset), do: list
|> CaesarHelper.rotate(offset)

def rot13(list), do: cypher(list, 13)
end

defmodule CaesarHelper do
def rotate(values, offset) do
values |> Enum.map(fn(x) -> adjust_to_char(x+rem(offset, 90)) end)
end

def adjust_to_char(value) when value > ?\z, do: rem(value, ?z) + ?\s
def adjust_to_char(value) when value < ?\s, do: ?z - rem(?\s - value, 90)
end

IO.puts Caesar.cypher([69,108,105,120,105,114], 3)                  # => Hol!Lu
IO.puts Caesar.cypher([72,111,108,33,108,117], -3)                  # => Elixir
IO.puts Caesar.cypher("Elixir rocks",1004)                          # => Szw,w&.&#qy'
IO.puts Caesar.cypher(Caesar.cypher("Elixir rocks", 1004), -1004)   # => Elixir rocks
IO.puts Caesar.rot13("Elixir rocks")                                # => Ryv+v%-%"px&
IO.puts Caesar.cypher(Caesar.rot13("Elixir rocks"), -13)            # => Elixir rocks
``````

My solution (currently doesn’t handle non-roman chars):

``````defprotocol Caesar do
@fallback_to_any true
def encrypt(string, shift)
def rot13(string)
end

defimpl Caesar, for: [List, BitString] do
def encrypt(string, shift \\ 13) when is_list(string) do
string |> Enum.map(fn letter ->
cond do
letter >= ?a and letter + shift <= ?z or
letter > ?A and letter + shift <= ?Z -> letter + shift
true -> letter - 26 + shift
end
end)
end
def encrypt(string, shift \\ 13) when is_binary(string) do
list = String.to_char_list string
encrypted = encrypt(list, shift)
List.to_string encrypted
end

def rot13(string) do
encrypt(string)
end
end

defimpl Caesar, for: Any do
def encrypt(_, _), do: raise "Dummy"
def rot13(_), do: raise "Dummy"
end
``````

Here’s a solution using comprehensions:

```defmodule CaesarFunc do

def shifter(n, shift) do
cond do
n in ?a..?z -> rem(n - ?a + rotate(shift), 26) + ?a
n in ?A..?Z -> rem(n - ?A + rotate(shift), 26) + ?A
true -> n
end
end

defp rotate (shift) do
if shift > 0 do
rem(shift, 26)
else
26 - rem(-shift, 26)
end
end

end

defprotocol Caesar do
def encrypt(string, shift)
def rot13(string)
end

defimpl Caesar, for: List do
def encrypt(string, shift) do
for n <- string , do: CaesarFunc.shifter(n, shift)
end
def rot13(string) do
encrypt(string, 13)
end
end

defimpl Caesar, for: BitString do
def encrypt(string, shift) do
for n <- to_charlist(string), do: CaesarFunc.shifter(n, shift)
end
def rot13(string) do
encrypt(string, 13)
end
end

list = ['abcdefghijklmnopqrstuvwxyz',
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
'The quick brown fox',
"ELIXIR",
"23Skidoo!!"]
for s <- list,
n <-[0,1,2,3,26,-1,-2,-25,-26,-53] do
IO.inspect Caesar.encrypt(s,n)
end
for s <- list do
IO.inspect Caesar.rot13(s)
IO.inspect Caesar.rot13(Caesar.rot13(s))
end
```
You must be logged in to comment