[Elixir]refactor to use pattern match in function instead of case one

exercism.ioで実装した問題を、 case 使って書いてたのですが、冗長なのでメソッドのパターンマッチを使って簡略化しました。

思いの外削減できたので、メモ。

特に、 can_attack? のところのパターンマッチは他の言語で書いた後に戻って書こうとするとパッと思い浮かばないときもある。。。

case でちんたら… が初めのやつ。pattern mach in functionが修正版。

case でちんたら…

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

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(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

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.