Elixir
  • My Elixir journey
  • Why functional programming?
  • Exhort
    • 🗓️Day 22
    • 🗓️Day 21
    • 🗓️Day 20
    • 🗓️Day 19
    • 🗓️Day 18
    • 🗓️Day 17
    • 🗓️Day 16
    • 🗓️Day 15
    • 🗓️Day 14
  • Bits and pieces
    • Clean mix dependencies
    • Run tests automatically on save
    • Run tests and stop on first failure
    • How to remove Tailwind from a Phoenix project
Powered by GitBook
On this page
  • Exercises
  • Newsletter
  • Chessboard
  • Remote Control Car
  • Overall progress
  1. Exhort

Day 16

Tuesday, 23 August 2022 - Day 16 of Exhort August 2022 - Exercism (My Elixir Journey)

PreviousDay 17NextDay 15

Last updated 2 years ago

Exercises

Newsletter

|

Solution

Expand to see code (spoiler alert)
defmodule Newsletter do
  @spec read_emails(path :: String.t()) :: list(String.t())
  def read_emails(path) do
    {:ok, contents} = File.read(path)
    String.split(contents, "\n", trim: true)
  end

  @spec open_log(path :: String.t()) :: pid()
  def open_log(path) do
    File.open!(path, [:write])
  end

  @spec log_sent_email(pid(), email :: String.t()) :: :ok
  def log_sent_email(pid, email) do
    IO.puts(pid, email)
  end

  @spec close_log(pid()) :: :ok
  def close_log(pid) do
    File.close(pid)
  end

  @spec send_newsletter(String.t(), String.t(), fun()) :: :ok
  def send_newsletter(emails_path, log_path, send_fun) do
    pid = open_log(log_path)

    read_emails(emails_path)
    |> Enum.each(fn email -> if :ok == send_fun.(email), do: log_sent_email(pid, email) end)

    close_log(pid)
  end
end

Notes

I had to use File.open! and I could have used IO.puts to avoid having to concatenate a \n.

Chessboard

|

Solution

Expand to see code (spoiler alert)
defmodule Chessboard do
  @spec rank_range :: Range.t()
  def rank_range, do: 1..8

  @spec file_range :: Range.t()
  def file_range, do: ?A..?H

  @spec ranks :: list(integer())
  def ranks, do: Enum.to_list(rank_range())

  @spec files :: list(String.t())
  def files, do: Enum.map(file_range(), &<<&1>>)
end

Notes

I really love the shorthand: Enum.map(file_range(), &<<&1>>)

Remote Control Car

Solution

Expand to see code (spoiler alert)
defmodule RemoteControlCar do
  @enforce_keys [:nickname]
  defstruct [:nickname, battery_percentage: 100, distance_driven_in_meters: 0]

  @spec new(String.t()) :: struct()
  def new(nickname \\ "none") do
    %RemoteControlCar{nickname: nickname}
  end

  @spec display_distance(remote_car :: struct()) :: String.t()
  def display_distance(%RemoteControlCar{} = remote_car) do
    "#{remote_car.distance_driven_in_meters} meters"
  end

  @spec display_battery(remote_car :: struct()) :: String.t()
  def display_battery(%RemoteControlCar{battery_percentage: 0}), do: "Battery empty"

  def display_battery(%RemoteControlCar{} = remote_car),
    do: "Battery at #{remote_car.battery_percentage}%"

  @spec drive(remote_car :: struct()) :: struct()
  def drive(%RemoteControlCar{battery_percentage: 0} = remote_car), do: remote_car

  def drive(%RemoteControlCar{} = remote_car) do
    %{
      remote_car
      | battery_percentage: remote_car.battery_percentage - 1,
        distance_driven_in_meters: remote_car.distance_driven_in_meters + 20
    }
  end
end

Notes

I didn't know about the @enforce_keys module attribute.

Overall progress

|

🗓️
View gist on GitHub
Exercise on Exercism
View my solution
View gist on GitHub
Exercise on Exercism
View my solution
View gist on GitHub
Exercise on Exercism
View my solution
Newsletter
Chessboard
Remote Control Car
Progress
An image showing my progress on Exercism. It's 17.9% as of Tuesday, 23 August 2022.