Care to prove you're more useful than an LLM?

I read an interesting post 2 days ago on HN:

Essentially, Peter Norvig found that no major LLM was able to produce a solution to the Cheryl’s Birthday logic puzzle that could handle other dates. He goes on to conjecture about what we may infer about the current limits of LLMs from that fact.

That discussion is interesting but I don’t necessarily want to wade into it here (though if you have strong thoughts feel free to share!). Instead, I found the idea that I could solve a simple logic and prove that I was more useful than an LLM captivating in a self-aggrandizing sort of way :stuck_out_tongue:

First, here’s my solution to that problem in Elixir:

Show/Hide Solution
defmodule CherylsBirthday do
  # 0. A knows M and B knows D
  # 1. A doesn't know D:          M has more than one day
  # 2. A knows B doesn't know M:  M must have no unique days
  # 3. B didn't know M at first:  D found in at least two months
  # 4. B knows M after 2.:        D must be in only one of the remaining months
  # 5. A knows D after 4.:        M must have only one unshared day among remaining months
  def solve(dates) do
    day_to_months = Enum.group_by(dates, &day/1, &month/1)
    month_to_days = Enum.group_by(dates, &month/1, &day/1)

    possibilities = %{days: Map.keys(day_to_months), months: Map.keys(month_to_days)}

    possibilities
    # 1. M has more than one day
    |> Map.update!(:months, fn months -> Enum.filter(months, &(length(month_to_days[&1]) > 1)) end)
    # 2. M must have no unique days
    |> Map.update!(:months, fn months ->
      Enum.filter(months, fn month ->
        Enum.all?(month_to_days[month], fn day -> length(day_to_months[day]) != 1 end)
      end)
    end)
    # 3. D found in at least two months
    |> Map.update!(:days, fn days -> Enum.filter(days, &(length(day_to_months[&1]) > 1)) end)
    # 4. D must be in only one of the remaining months
    |> then(fn %{days: days, months: months} = possibilities ->
      days = Enum.filter(days, fn day -> Enum.count(months, &(day in month_to_days[&1])) == 1 end)
      %{possibilities | days: days}
    end)
    # 5. M must have only one unshared day among remaining months
    |> then(fn %{months: months} = possibilities ->
      {months, days} =
        months
        |> Enum.map(fn month ->
          unshared_days =
            Enum.filter(month_to_days[month], fn day ->
              Enum.all?(months -- [month], &(day not in month_to_days[&1]))
            end)

          {month, unshared_days}
        end)
        |> Enum.filter(fn {_month, unshared_days} -> length(unshared_days) == 1 end)
        |> Enum.unzip()

      %{possibilities | days: List.flatten(days), months: months}
    end)
    |> then(fn %{days: [day], months: [month]} -> {month, day} end)
  end

  def day({_month, day}), do: day
  def month({month, _day}), do: month

  def original_dates do
    may = {"May", [15, 16, 19]}
    jun = {"Jun", [17, 18]}
    jul = {"Jul", [14, 16]}
    aug = {"Aug", [14, 15, 17]}
    for {month, days} <- [may, jun, jul, aug], day <- days, do: {month, day}
  end

  def alternate_dates do
    jan = {"Jan", [4, 15]}
    mar = {"Mar", [13, 24]}
    may = {"May", [11, 17, 30]}
    jul = {"Jul", [13, 24, 30]}
    for {month, days} <- [jan, mar, may, jul], day <- days, do: {month, day}
  end
end

CherylsBirthday.original_dates()
|> CherylsBirthday.solve()
|> IO.inspect()
#=> {"Jul", 16}

CherylsBirthday.alternate_dates()
|> CherylsBirthday.solve()
|> IO.inspect()
#=> {"Jul", 30}

This was a fun distraction for a Sunday morning. But the reason I’m making this post is that when I finally peeked at his solution, it was wildly different from my own. (And honestly more “functional” despite being in Python which annoyed me :sweat_smile:)

Now I’m curious how other Elixir programmers would approach this problem! I discourage everyone from cheating of course. That would defeat the point of the exercise. But if you’re looking for a distraction too, I recommend giving this a shot!


To get you started, here are two sets of dates each with their expected answer:

  • Original
    • Dates: May 15, May 16, May 19, June 17, June 18, July 14, July 16, August 14, August 15, August 17
    • Answer: July 16
  • Alternate
    • Dates: January 4, January 15, March 13, March 24, May 11, May 17, May 30, July 13, July 24, July 30
    • Answer: July 30
1 Like