exercism.ioで実装した問題を、 case 使って書いてたのですが、冗長なのでメソッドのパターンマッチを使って簡略化しました。
思いの外削減できたので、メモ。
特に、 can_attack? のところのパターンマッチは他の言語で書いた後に戻って書こうとするとパッと思い浮かばないときもある。。。
case でちんたら… が初めのやつ。pattern mach in functionが修正版。
defmodule Queens do
@type t :: %Queens{ black: {integer, integer}, white: {integer, integer} }
defstruct black: {7, 3}, white: {0, 3}
@doc """
Creates a new set of Queens
"""
@spec new(nil | list) :: Queens.t()
def new(positions \\ nil) do
%__MODULE__{}
|> get_and_update(positions, :white)
|> get_and_update(positions, :black)
end
defp get_and_update(map, nil, _key_atom), do: map
defp get_and_update(map, positions, key_atom) when key_atom in [:black, :white] do
case List.keytake(positions, key_atom, 0) do
nil ->
map
{{_key, val}, _other} ->
map
|> valid_position(key_atom, val)
|> Map.put(key_atom, val)
end
end
defp valid_position(map, :white, val), do: valid(map, :black, val)
defp valid_position(map, :black, val), do: valid(map, :white, val)
defp valid(map, key, val) when key in [:black, :white] do
case Map.fetch!(map, key) do
^val ->
raise ArgumentError
_ ->
map
end
end
@doc """
Gives a string reprentation of the board with
white and black queen locations shown
"""
@spec to_string(Queens.t()) :: String.t()
def to_string(queens) do
white = queens.white
black = queens.black
for x <- 0..7,
y <- 0..7 do
case {x, y} do
^white -> {{x, y}, "W"}
^black -> {{x, y}, "B"}
{_, _} -> {{x, y}, "_"}
end
end
|> format
end
defp format(list) do
list
|> Enum.reduce("", fn {{x, y}, mark}, acc ->
case {x, y} do
{7, 7} -> acc <> mark
{_, 7} -> acc <> mark <> "\n"
_ -> acc <> mark <> " "
end
end)
end
@doc """
Checks if the queens can attack each other
"""
@spec can_attack?(Queens.t()) :: boolean
def can_attack?(queens) do
white = queens.white
black = queens.black
case { elem(white, 0) - elem(black, 0), elem(white, 1) - elem(black, 1)} do
{x, y} when (x == y) or (x == -y) -> true
{x, y} when (x in [-1, 0, 1]) or (y in [-1, 0, 1]) -> true
{_, _} -> false
end
end
end
defmodule Queens do
@type t :: %Queens{ black: {integer, integer}, white: {integer, integer} }
defstruct black: {7, 3}, white: {0, 3}
@doc """
Creates a new set of Queens
"""
@spec new(nil | list) :: Queens.t()
def new(white: same_position, black: same_position), do: raise ArgumentError
def new(white: white, black: black), do: %__MODULE__{white: white, black: black}
def new, do: %__MODULE__{}
@doc """
Gives a string reprentation of the board with
white and black queen locations shown
"""
@spec to_string(Queens.t()) :: String.t()
def to_string(queens) do
white = queens.white
black = queens.black
for x <- 0..7,
y <- 0..7 do
case {x, y} do
^white -> {{x, y}, "W"}
^black -> {{x, y}, "B"}
{_, _} -> {{x, y}, "_"}
end
end
|> format
end
defp format(list), do: Enum.reduce(list, "", fn {{x, y}, mark}, acc -> insert_mark acc, {x, y}, mark end)
defp insert_mark(acc, {7, 7}, mark), do: acc <> mark
defp insert_mark(acc, {_, 7}, mark), do: acc <> mark <> "\n"
defp insert_mark(acc, {_, _}, mark), do: acc <> mark <> " "
@doc """
Checks if the queens can attack each other
"""
@spec can_attack?(Queens.t()) :: boolean
def can_attack?(%Queens{:white => {white_x, white_y}, :black => {black_x, black_y}}) do
is_attackable_position? white_x - black_x, white_y - black_y
end
defp is_attackable_position?(x, y) when abs(x) == abs(y), do: true
defp is_attackable_position?(x, y) when x == 0 or y == 0, do: true
defp is_attackable_position?(_, _), do: false
end