Home Posts Post Search Tag Search

Advanced Functional Elixir - 02 - Act on It
Published on: 2026-03-05 Tags: Blog, Side Project, Advanced Functional Programming, FunPark, Act on It

Act on It

Create a contra_map/1 that normalizes strings before comparing them - make sure that they are trimmed and use the same case.

### FunPark.Eq.Utils
  @doc """
  Build an equality map that first normalizes strings before comparing.

  The provided function will trim whitespace and downcase binaries so that
  comparisons are case-insensitive and ignore surrounding spaces. The
  returned map can be passed to `contramap/2` or used directly with the
  helpers `eq?/3` and `not_eq?/3`.
  """
  def contramap_string(eq \\ Eq) do
    contramap(&normalize_string/1, eq)
  end

  defp normalize_string(str) when is_binary(str) do
    str
    |> String.trim()
    |> String.downcase()
  end

  defp normalize_string(other), do: other

Now that I have that we can test with a few values.

iex> eq_map = FunPark.Eq.Utils.contramap_string()
%{eq?: #Function<…>, not_eq?: #Function<…>}

iex> FunPark.Eq.Utils.eq?(" Hello ", "hello", eq_map)
true

iex> FunPark.Eq.Utils.eq?("Hello", "HELLO", eq_map)
true

Now to push this even further I wanted to and an other that tests integers. I picked so long as the integer value above the hundreds is the same (1120 == 1180) it will evaluate to true

  @doc """
  Build a map that first normalizes an integer before comparing.

  This will make sure that as long as the values above the 100's digit is
  the same it will evaluate to the equal. It will also make sure that if
  the value is less than 100 it will always be the same.
  """
  def contramap_integer(eq \\ Eq) do
    contramap(&normalize_integer/1, eq)
  end

  defp normalize_integer(int) when is_integer(int) do
    hundreds = rem(int, 100)
    int - hundreds
  end

  defp normalize_integer(other), do: other

Now here are a few tests.

iex(46)> FunPark.Eq.Utils.eq?(1000, 1020)
false
iex(47)> eq_int_map = FunPark.Eq.Utils.contramap_integer()
%{
  eq?: #Function<0.114186896/2 in FunPark.Eq.Utils.contramap/2>,
  not_eq?: #Function<1.114186896/2 in FunPark.Eq.Utils.contramap/2>
}
iex(48)> FunPark.Eq.Utils.eq?(1000, 1020, eq_int_map)
true
iex(49)> FunPark.Eq.Utils.eq?(1080, 1020, eq_int_map)
true

A Patron wants to give their FastPasses to a friend. How would you check that none of the friends pass are for the same time?


Let’s assume the following passes.

iex(50)> pass_a = FunPark.FastPass.make(tea_cup, datetime)
%FunPark.FastPass{
  id: 1230,
  ride: %FunPark.Ride{
    id: 1090,
    name: "Tea Cup",
    min_age: 0,
    min_height: 0,
    wait_time: 0,
    online: true,
    tags: []
  },
  time: ~U[2025-06-01 13:00:00Z]
}
iex(51)> pass_b = FunPark.FastPass.make(mansion, datetime)
%FunPark.FastPass{
  id: 1294,
  ride: %FunPark.Ride{
    id: 1026,
    name: "Dark Mansion",
    min_age: 14,
    min_height: 0,
    wait_time: 0,
    online: true,
    tags: [:dark]
  },
  time: ~U[2025-06-01 13:00:00Z]
}
iex(52)> datetime_2 = DateTime.new!(~D[2025-06-01], ~T[13:01:01])
~U[2025-06-01 13:01:01Z]
iex(53)> pass_c = FunPark.FastPass.make(mansion, datetime_2)
%FunPark.FastPass{
  id: 1486,
  ride: %FunPark.Ride{
    id: 1026,
    name: "Dark Mansion",
    min_age: 14,
    min_height: 0,
    wait_time: 0,
    online: true,
    tags: [:dark]
  },
  time: ~U[2025-06-01 13:01:01Z]
}
iex(54)> datetime_3 = DateTime.new!(~D[2025-06-01], ~T[13:11:01])
~U[2025-06-01 13:11:01Z]
iex(55)> pass_d = FunPark.FastPass.make(haunted_mansion, datetime_3)
%FunPark.FastPass{
  id: 1678,
  ride: %FunPark.Ride{
    id: 578,
    name: "Haunted Mansion",
    min_age: 0,
    min_height: 0,
    wait_time: 0,
    online: true,
    tags: []
  },
  time: ~U[2025-06-01 13:11:01Z]
}
iex(56)> jims_passes = [pass_a, pass_c, pass_d] 
[
  %FunPark.FastPass{
    id: 1230,
    ride: %FunPark.Ride{
      id: 1090,
      name: "Tea Cup",
      min_age: 0,
      min_height: 0,
      wait_time: 0,
      online: true,
      tags: []
    },
    time: ~U[2025-06-01 13:00:00Z]
  },
  %FunPark.FastPass{
    id: 1486,
    ride: %FunPark.Ride{
      id: 1026,
      name: "Dark Mansion",
      min_age: 14,
      min_height: 0,
      wait_time: 0,
      online: true,
      tags: [:dark]
    },
    time: ~U[2025-06-01 13:01:01Z]
  },
  %FunPark.FastPass{
    id: 1678,
    ride: %FunPark.Ride{
      id: 578,
      name: "Haunted Mansion",
      min_age: 0,
      min_height: 0,
      wait_time: 0,
      online: true,
      tags: []
    },
    time: ~U[2025-06-01 13:11:01Z]
  }
]
iex(57)> sals_passes = [pass_b]
[
  %FunPark.FastPass{
    id: 1294,
    ride: %FunPark.Ride{
      id: 1026,
      name: "Dark Mansion",
      min_age: 14,
      min_height: 0,
      wait_time: 0,
      online: true,
      tags: [:dark]
    },
    time: ~U[2025-06-01 13:00:00Z]
  }
]

If Jim wants to give sal his passes we should only be able to give him pass_c, and pass_d. As a and b share the same time.


How can we check to see if the passes are the same (time wise)

iex(58)> no_clashes? = 
...(58)>   FunPark.List.difference(sals_passes, jims_passes, FunPark.FastPass.eq_time()) == sals_passes
false
iex(59)> FunPark.List.difference(sals_passes, jims_passes, FunPark.FastPass.eq_time())
[]
iex(60)> FunPark.List.difference(jims_passes, sals_passes, FunPark.FastPass.eq_time())
[
  %FunPark.FastPass{
    id: 1486,
    ride: %FunPark.Ride{
      id: 1026,
      name: "Dark Mansion",
      min_age: 14,
      min_height: 0,
      wait_time: 0,
      online: true,
      tags: [:dark]
    },
    time: ~U[2025-06-01 13:01:01Z]
  },
  %FunPark.FastPass{
    id: 1678,
    ride: %FunPark.Ride{
      id: 578,
      name: "Haunted Mansion",
      min_age: 0,
      min_height: 0,
      wait_time: 0,
      online: true,
      tags: []
    },
    time: ~U[2025-06-01 13:11:01Z]
  }
]