고대 중동의 암호화 알고리즘을 간단하게 구현하는 문제였다. 이런 치환암호(substitution cipher) 방식의 알고리즘은 엘릭서에서는 문자 리스트로 바꿔서 처리하는 것이 간단하다. 배경 지식인 건지 내가 텍스트에서 못 찾은 건지, 설명에 몇 가지 내용이 빠져 있어서 테스트 케이스를 더듬어가며 답을 찾아야 하는 과정이 조금 번거로웠다. MMI(나머지 연산의 역원)를 구하는 과정에 스트림을 쓴 것이 내심 만족스럽다.
defmodule AffineCipher do
@typedoc """
A type for the encryption key
"""
@type key() :: %{a: integer, b: integer}
@doc """
Encode an encrypted message using a key
"""
@spec encode(key :: key(), message :: String.t()) :: {:ok, String.t()} | {:error, String.t()}
def encode(%{a: a}, _message) when rem(a, 2) == 0 or rem(a, 13) == 0 do
{:error, "a and m must be coprime."}
end
def encode(%{a: a, b: b}, message) do
message
|> String.replace(~r/[\s\.\,]/, "")
|> to_charlist()
|> Enum.map(fn
c when c in ?a..?z ->
?a + rem(a * (c - ?a) + b, 26)
c when c in ?A..?Z ->
?a + rem(a * (c - ?A) + b, 26)
c ->
c
end)
|> Enum.chunk_every(5)
|> Enum.map(&to_string/1)
|> Enum.join(" ")
|> then(fn r -> {:ok, r} end)
end
@doc """
Decode an encrypted message using a key
"""
@spec decode(key :: key(), message :: String.t()) :: {:ok, String.t()} | {:error, String.t()}
def decode(%{a: a}, _encrypted) when rem(a, 2) == 0 or rem(a, 13) == 0 do
{:error, "a and m must be coprime."}
end
def decode(%{a: a, b: b}, encrypted) do
encrypted
|> String.replace(~r/\s/, "")
|> to_charlist()
|> Enum.map(fn
y when y in ?a..?z ->
?a + Integer.mod(mmi(a) * (y - ?a - b), 26)
y ->
y
end)
|> to_string()
|> then(fn r -> {:ok, r} end)
end
defp mmi(a) do
Stream.iterate(1, &(&1 + 1))
|> Stream.filter(fn i ->
rem(a * i, 26) == 1
end)
|> Enum.take(1)
|> hd()
end
end
'호두나무 공방 > Exercism in Elixir' 카테고리의 다른 글
Diamond - Exercism in Elixir (0) | 2022.10.12 |
---|---|
Palindrome Products - Exercism in Elixir (0) | 2022.10.11 |
Tournament - Exercism in Elixir (0) | 2022.10.05 |
Phone Number - Exercism in Elixir (0) | 2022.10.04 |
Grep - Exercism in Elixir (1) | 2022.10.03 |