diff --git a/ipynb/CherylMind.ipynb b/ipynb/CherylMind.ipynb
new file mode 100644
index 0000000..4fd561f
--- /dev/null
+++ b/ipynb/CherylMind.ipynb
@@ -0,0 +1,545 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "e21c9af1-0087-440b-8dfe-758e0361f6e9",
+ "metadata": {},
+ "source": [
+ "
Peter Norvig
Sept 25, 2024
\n",
+ "\n",
+ "# LLMs, Theory of Mind, and Cheryl's Birthday\n",
+ "\n",
+ "There has been [much](https://spectrum.ieee.org/theory-of-mind-ai) [debate](https://aclanthology.org/2023.conll-1.25/) [on](https://www.gsb.stanford.edu/faculty-research/working-papers/theory-mind-may-have-spontaneously-emerged-large-language-models) [the](https://arxiv.org/abs/2302.02083) [degree](https://www.nature.com/articles/s41562-024-01882-z) to which Large Language Models (LLMs) have a theory of mind: a way of understanding what other people know and don't know. In this notebook I explore one small part of the issue by asking six LLMs to solve the [Cheryl's Birthday Problem](https://en.wikipedia.org/wiki/Cheryl%27s_Birthday), a well-known logic puzzle in which different characters have different states of knowledge. \n",
+ "\n",
+ "**TLDR**: The six LLMs were all familiar with the problem, so I didn't have to describe it in the prompt, just name it. They were able to correctly state that the answer to the problem is July 16. But none of them were able to write a program that finds the solution. They all failed to distinguish the different knowledge states of the different characters–for this problem they had no theory of mind.\n",
+ "\n",
+ "Below I show the response for each LLM. I elided some of the explanatory output when it was very similar to previous output, but I show the programs verbatim. My comments are in *[bracketed italics]* and the LLM output is in regular upright text. The queries were made on Sept 25, 2024; subsequent updates of the models may perform differently.\n",
+ "\n",
+ "For each LLMs the prompt was:\n",
+ "\n",
+ "***What is the answer to the \"Cheryl's Birthday\" problem? Write a Python program to solve it. Make sure that the program will still work if the list of possible dates is changed.***"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c3e532bf-67b0-4976-aa5f-730401a4ef07",
+ "metadata": {},
+ "source": [
+ "# Human\n",
+ "\n",
+ "An actual human, me, was able to write a [**program**](https://github.com/norvig/pytudes/blob/main/ipynb/Cheryl-and-Eve.ipynb) that correctly solves the original problem and also handles new sets of dates."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "55f6744e-356c-4fd8-af26-e7559618dd8d",
+ "metadata": {},
+ "source": [
+ "# ChatGPT 4o\n",
+ "\n",
+ "*[ChatGPT 4o gives the following response, which starts with an excellent summary of the problem:]*\n",
+ "\n",
+ "The \"Cheryl's Birthday\" problem is a well-known logic puzzle that can be solved using logical deduction. Let's first break down the problem before writing a Python program.\n",
+ "\n",
+ "### Problem Statement\n",
+ "Cheryl gives Albert and Bernard a list of 10 possible birthdates, and tells them different information:\n",
+ "- Albert knows the month.\n",
+ "- Bernard knows the day.\n",
+ "\n",
+ "Cheryl provides the following 10 possible dates:\n",
+ "- May 15, May 16, May 19\n",
+ "- June 17, June 18\n",
+ "- July 14, July 16\n",
+ "- August 14, August 15, August 17\n",
+ "\n",
+ "Albert then says: \"I don't know when Cheryl's birthday is, but I know that Bernard doesn't know either.\"\n",
+ "\n",
+ "Bernard replies: \"At first I didn’t know when Cheryl's birthday is, but now I know.\"\n",
+ "\n",
+ "Albert then says: \"Now I know when Cheryl's birthday is.\"\n",
+ "\n",
+ "### Steps for Deduction\n",
+ "1. From Albert's statement, we can deduce that Cheryl's birthday is not in May or June, because if it were, Bernard could possibly know the exact birthday if the day was unique (May 19 or June 18). Since Albert is sure Bernard doesn't know, we can eliminate May and June.\n",
+ "2. After eliminating May and June, the remaining possible dates are:\n",
+ " - July 14, July 16\n",
+ " - August 14, August 15, August 17\n",
+ "3. From Bernard’s statement, now knowing the birthday, we can deduce that Cheryl's birthday isn't on July 14 or August 14, because if it were, Bernard would still be uncertain (since both dates share the day 14).\n",
+ "4. The remaining possible dates are:\n",
+ " - July 16\n",
+ " - August 15\n",
+ " - August 17\n",
+ "5. Finally, from Albert’s statement, we deduce that Cheryl's birthday must be in July, because if it were in August, Albert wouldn’t be able to know the exact date. Thus, the birthday is July 16.\n",
+ "\n",
+ "### Python Program to Solve the Problem\n",
+ "We can now write a Python program to generalize this solution and ensure it works for any list of possible dates.\n",
+ "\n",
+ "### Explanation:\n",
+ "- Step 1: The function groups the dates by month and by day.\n",
+ "- Step 2: Based on Albert's statement, we eliminate the months of May and June.\n",
+ "- Step 3: Based on Bernard's statement, we eliminate dates where the day appears more than once (i.e., both July 14 and August 14 are eliminated).\n",
+ "- Step 4: Albert now knows Cheryl's birthday, so it must be in the month where only one date is left (i.e., July).\n",
+ "\n",
+ "The program is designed to work with any given list of possible dates, not just the ones in the original problem."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "8a1ca966-d8aa-4bc5-bbe3-253b3b62d538",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Cheryl's birthday is: No solution found\n"
+ ]
+ }
+ ],
+ "source": [
+ "def filter_dates(possible_dates):\n",
+ " # Step 1: Group dates by month and day\n",
+ " month_map = {}\n",
+ " day_map = {}\n",
+ " \n",
+ " for date in possible_dates:\n",
+ " month, day = date.split()\n",
+ " day = int(day)\n",
+ " \n",
+ " # Group by month\n",
+ " if month not in month_map:\n",
+ " month_map[month] = []\n",
+ " month_map[month].append(day)\n",
+ " \n",
+ " # Group by day\n",
+ " if day not in day_map:\n",
+ " day_map[day] = []\n",
+ " day_map[day].append(month)\n",
+ " \n",
+ " # Step 2: Albert says: \"I don't know when Cheryl's birthday is, but I know Bernard doesn't know either.\"\n",
+ " # This implies Cheryl's birthday cannot be in May or June because these months have unique days (19th and 18th).\n",
+ " possible_dates = [date for date in possible_dates if date.split()[0] not in ('May', 'June')]\n",
+ " \n",
+ " # Step 3: Bernard now knows the birthday, so it cannot be a day that appears more than once.\n",
+ " possible_dates = [date for date in possible_dates if len(day_map[int(date.split()[1])]) == 1]\n",
+ " \n",
+ " # Step 4: Albert now knows the birthday, so it must be in a unique month remaining after Bernard's elimination.\n",
+ " remaining_months = set(date.split()[0] for date in possible_dates)\n",
+ " for date in possible_dates:\n",
+ " month = date.split()[0]\n",
+ " if len(month_map[month]) == 1:\n",
+ " return date\n",
+ " \n",
+ " return \"No solution found\"\n",
+ "\n",
+ "# Test the program with the given list of possible dates\n",
+ "possible_dates = [\n",
+ " \"May 15\", \"May 16\", \"May 19\",\n",
+ " \"June 17\", \"June 18\",\n",
+ " \"July 14\", \"July 16\",\n",
+ " \"August 14\", \"August 15\", \"August 17\"\n",
+ "]\n",
+ "\n",
+ "cheryls_birthday = filter_dates(possible_dates)\n",
+ "print(\"Cheryl's birthday is:\", cheryls_birthday)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "cb513413-f71d-476d-a6bc-211ab1589bbe",
+ "metadata": {},
+ "source": [
+ "*[We see that the program fails to find a solution. It fails because it has a single variable for possible dates, when it should be keeping track of separate possible dates for Albert and Bernard.]* "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d981531c-31f6-442e-b210-d0bff93d49bc",
+ "metadata": {},
+ "source": [
+ "# Gemini Advanced\n",
+ "\n",
+ "*[Gemini's response:]*\n",
+ "\n",
+ "The answer to Cheryl's Birthday problem is **July 16th**.\n",
+ "\n",
+ "Here's the Python program, modified to handle changes in the list of possible dates:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "7c0bd1e7-4ada-4b43-97a5-674f615b9f77",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Unable to determine Cheryl's birthday\n",
+ "With the new set of dates, unable to determine Cheryl's birthday\n"
+ ]
+ }
+ ],
+ "source": [
+ "def cheryl_birthday(possible_dates):\n",
+ " \"\"\"Solves the Cheryl's Birthday logic problem, allowing for a variable list of possible dates.\"\"\"\n",
+ "\n",
+ " # Possible months\n",
+ " months = [\"May\", \"June\", \"July\", \"August\"]\n",
+ "\n",
+ " # Albert's statement eliminates some possibilities\n",
+ " # He knows the month, so there must be only one possible date in that month\n",
+ " for month in months:\n",
+ " if len([date for date in possible_dates if (month, date) not in [(\"May\", 19), (\"June\", 18)]]) == 1:\n",
+ " months = [month]\n",
+ " break\n",
+ "\n",
+ " # Bernard's statement further narrows down the possibilities\n",
+ " # He knows the date, so there must be only one possible month for that date\n",
+ " for date in possible_dates:\n",
+ " if len([month for month in months if (month, date) not in [(\"May\", 19), (\"June\", 18)]]) == 1:\n",
+ " possible_dates = [date]\n",
+ " break\n",
+ "\n",
+ " # After Bernard's statement, Albert also knows the answer\n",
+ " # This means there's only one possible date left in the remaining month\n",
+ " for month in months:\n",
+ " if len([date for date in possible_dates if (month, date) not in [(\"May\", 19), (\"June\", 18)]]) == 1:\n",
+ " possible_dates = [date]\n",
+ " break\n",
+ "\n",
+ " # Now there should be only one possibility left\n",
+ " if len(months) == 1 and len(possible_dates) == 1:\n",
+ " return months[0], possible_dates[0]\n",
+ " else:\n",
+ " return None\n",
+ "\n",
+ "# Original problem setup\n",
+ "original_dates = [14, 15, 16, 17, 18, 19]\n",
+ "solution = cheryl_birthday(original_dates)\n",
+ "\n",
+ "if solution:\n",
+ " print(f\"Cheryl's birthday is on {solution[0]} {solution[1]}\")\n",
+ "else:\n",
+ " print(\"Unable to determine Cheryl's birthday\")\n",
+ "\n",
+ "# Example with a different set of possible dates\n",
+ "new_dates = [13, 14, 15, 16, 17]\n",
+ "new_solution = cheryl_birthday(new_dates)\n",
+ "\n",
+ "if new_solution:\n",
+ " print(f\"With the new set of dates, Cheryl's birthday is on {new_solution[0]} {new_solution[1]}\")\n",
+ "else:\n",
+ " print(\"With the new set of dates, unable to determine Cheryl's birthday\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "11f9bfc5-8548-4f7a-ac69-acecddb6f19b",
+ "metadata": {},
+ "source": [
+ "*[Gemini correctly states the answer, but then produces a program that fails to find the correct answer. Again, the failure is due to having one list of possible dates. The program accepts a list of \"possible_dates\", but confusingly, those are actually just days of the month; you aren't allowed to pass in different month/day combinations. And even if you do pass in different days, the program hard-codes certain days and months.]*"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3932858f-170b-4979-a565-c3b41a74e7fe",
+ "metadata": {},
+ "source": [
+ "# Llama 405B\n",
+ "\n",
+ "*[Llama gives a fine introduction to the problem, which I omit. Here is the program it produces:]*"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "1faff935-74aa-44ee-9f37-15f71521e7d2",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Possible dates for Cheryl's birthday\n",
+ "dates = [\n",
+ " (5, 13), (5, 15), (5, 16), \n",
+ " (6, 13), (6, 15), (6, 16), \n",
+ " (7, 13), (7, 15), (7, 16), \n",
+ " (8, 13), (8, 15), (8, 16)\n",
+ "]\n",
+ "\n",
+ "# Function to check if a date is possible based on Albert's knowledge\n",
+ "def is_possible_albert(month):\n",
+ " return all(m == month for m, _ in dates)\n",
+ "\n",
+ "# Function to check if a date is possible based on Bernard's knowledge\n",
+ "def is_possible_bernard(day):\n",
+ " return any((m, d) == (month, day) for m, d in dates)\n",
+ "\n",
+ "# Function to check if Albert can deduce the birthday\n",
+ "def albert_deduce(month):\n",
+ " return len([d for m, d in dates if m == month]) == 1\n",
+ "\n",
+ "# Function to check if Bernard can deduce the birthday\n",
+ "def bernard_deduce(day):\n",
+ " return len([m for m, d in dates if d == day]) == 1\n",
+ "\n",
+ "# Main program\n",
+ "for month, day in dates:\n",
+ " if not is_possible_albert(month):\n",
+ " continue\n",
+ " if not is_possible_bernard(day):\n",
+ " continue\n",
+ " if albert_deduce(month):\n",
+ " print(f\"Cheryl's birthday is {month}/{day}\")\n",
+ " break"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "469c146d-bbc9-4b2f-8609-b82631a5139d",
+ "metadata": {},
+ "source": [
+ "*[There is no output from this program, which means it failed to find the date. This program is more flawed than the others. I do like that it defines functions for the various pieces, but note that the function \"bernard_deduce\" is defined but never called.]*"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "02b1f7fd-918c-432d-8784-95dff3d4f86e",
+ "metadata": {},
+ "source": [
+ "# Claude 3.5 Sonnet\n",
+ "\n",
+ "*[Again, Claude provides a nice explanation of the problem, which I omit; here is the program:]*"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "1da1319a-95d1-4c98-8b36-d96a8c552cb7",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Cheryl's birthday is on: No unique solution found.\n",
+ "With the new set of dates, Cheryl's birthday is on: No unique solution found.\n"
+ ]
+ }
+ ],
+ "source": [
+ "from itertools import product\n",
+ "\n",
+ "def solve_cheryls_birthday(dates):\n",
+ " # Extract unique months and days\n",
+ " months = sorted(set(date.split()[0] for date in dates))\n",
+ " days = sorted(set(int(date.split()[1]) for date in dates))\n",
+ "\n",
+ " # Step 1: Albert knows the month, Bernard knows the day\n",
+ " possible_months = [m for m in months if len([d for d in dates if d.startswith(m)]) > 1]\n",
+ " possible_days = [d for d in days if len([date for date in dates if date.endswith(str(d))]) > 1]\n",
+ "\n",
+ " # Step 2: Albert knows Bernard doesn't know\n",
+ " possible_dates = [date for date in dates if int(date.split()[1]) in possible_days]\n",
+ "\n",
+ " # Step 3: Bernard now knows\n",
+ " possible_dates = [date for date in possible_dates if possible_dates.count(date.split()[1]) == 1]\n",
+ "\n",
+ " # Step 4: Albert now knows\n",
+ " result = [date for date in possible_dates if possible_dates.count(date.split()[0]) == 1]\n",
+ "\n",
+ " return result[0] if result else \"No unique solution found.\"\n",
+ "\n",
+ "# Example usage\n",
+ "dates = [\n",
+ " \"May 15\", \"May 16\", \"May 19\",\n",
+ " \"June 17\", \"June 18\",\n",
+ " \"July 14\", \"July 16\",\n",
+ " \"August 14\", \"August 15\", \"August 17\"\n",
+ "]\n",
+ "\n",
+ "solution = solve_cheryls_birthday(dates)\n",
+ "print(f\"Cheryl's birthday is on: {solution}\")\n",
+ "\n",
+ "# Test with a different set of dates\n",
+ "new_dates = [\n",
+ " \"April 15\", \"April 16\", \"April 25\",\n",
+ " \"May 15\", \"May 16\",\n",
+ " \"June 17\", \"June 18\",\n",
+ " \"July 14\", \"July 16\", \"July 17\"\n",
+ "]\n",
+ "\n",
+ "new_solution = solve_cheryls_birthday(new_dates)\n",
+ "print(f\"With the new set of dates, Cheryl's birthday is on: {new_solution}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9b16c634-5eda-4249-97fd-e61c980dadbe",
+ "metadata": {},
+ "source": [
+ "*[Again, the program fails. It has the same issue with a single possible_dates variable.]*"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "52003af0-0ea1-4ed4-9b5e-5b9b8862a068",
+ "metadata": {},
+ "source": [
+ "# Perplexity\n",
+ "\n",
+ "*[Here is Perplexity's code, with its explanation omitted:]*"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "55e99206-42f8-44ea-a091-1ca6a14f4e89",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Cheryl's birthday is: None\n"
+ ]
+ }
+ ],
+ "source": [
+ "from collections import defaultdict\n",
+ "\n",
+ "def solve_cheryls_birthday(possible_dates):\n",
+ " months = defaultdict(list)\n",
+ " days = defaultdict(list)\n",
+ " \n",
+ " for date in possible_dates:\n",
+ " month, day = date.split()\n",
+ " months[month].append(date)\n",
+ " days[day].append(date)\n",
+ " \n",
+ " # Albert knows the month, Bernard knows the day\n",
+ " possible = [date for date in possible_dates if len(months[date.split()[0]]) > 1 and len(days[date.split()[1]]) > 1]\n",
+ " \n",
+ " # Bernard now knows the date\n",
+ " possible = [date for date in possible if len([d for d in possible if d.split()[1] == date.split()[1]]) == 1]\n",
+ " \n",
+ " # Albert now knows the date\n",
+ " possible = [date for date in possible if len([d for d in possible if d.split()[0] == date.split()[0]]) == 1]\n",
+ " \n",
+ " return possible[0] if len(possible) == 1 else None\n",
+ "\n",
+ "# Example usage\n",
+ "possible_dates = [\n",
+ " \"May 15\", \"May 16\", \"May 19\",\n",
+ " \"June 17\", \"June 18\",\n",
+ " \"July 14\", \"July 16\",\n",
+ " \"August 14\", \"August 15\", \"August 17\"\n",
+ "]\n",
+ "\n",
+ "result = solve_cheryls_birthday(possible_dates)\n",
+ "print(f\"Cheryl's birthday is: {result}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b9296a89-b55c-40aa-8fc0-788cea3511e0",
+ "metadata": {},
+ "source": [
+ "*[Once again, this program has the same mistake.]*"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "4b4e47b5-f5a2-491d-ad34-f8c08aceca59",
+ "metadata": {},
+ "source": [
+ "# Cohere Chat Playground\n",
+ "\n",
+ "*[Cohere gives a nice explanation, and then this program:]*"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "f1c6bfd7-6738-4325-8bc4-f91b4ee15e48",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Cheryl's birthday is: The solution could not be determined.\n"
+ ]
+ }
+ ],
+ "source": [
+ "def solve_cheryls_birthday(possible_dates):\n",
+ " # Create a dictionary to map months to their respective days\n",
+ " month_days = {\n",
+ " \"May\": [15, 16, 19],\n",
+ " \"June\": [17, 18],\n",
+ " \"July\": [14, 16],\n",
+ " \"August\": [14, 15, 17],\n",
+ " }\n",
+ "\n",
+ " # Albert knows the month, so we can filter out the possible dates based on that\n",
+ " albert_knows = [date for date in possible_dates if date[1] in month_days]\n",
+ "\n",
+ " # Bernard knows the day, so we can further filter based on that\n",
+ " bernard_knows = [date for date in albert_knows if date[2] in month_days[date[1]]]\n",
+ "\n",
+ " # If Bernard knows the birthday, Albert should also know it\n",
+ " if len(bernard_knows) == 1:\n",
+ " return bernard_knows[0]\n",
+ " else:\n",
+ " return \"The solution could not be determined.\"\n",
+ "\n",
+ "# Example usage:\n",
+ "possible_dates = [\n",
+ " (\"May\", 15, \"Friday\"),\n",
+ " (\"May\", 16, \"Saturday\"),\n",
+ " (\"May\", 19, \"Tuesday\"),\n",
+ " (\"June\", 17, \"Thursday\"),\n",
+ " (\"June\", 18, \"Friday\"),\n",
+ " (\"July\", 14, \"Monday\"),\n",
+ " (\"July\", 16, \"Wednesday\"),\n",
+ " (\"August\", 14, \"Wednesday\"),\n",
+ " (\"August\", 15, \"Thursday\"),\n",
+ " (\"August\", 17, \"Saturday\"),\n",
+ "]\n",
+ "\n",
+ "solution = solve_cheryls_birthday(possible_dates)\n",
+ "print(f\"Cheryl's birthday is: {solution}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9d10112a-299e-49be-96ce-cf9551564894",
+ "metadata": {},
+ "source": [
+ "*[Again, the program fails to find a solution because it doesn't distinguish who knows what (note that the \"bernard_knows\" variable is defined in terms of \"albert_knows\", but Bernard does not know what Albert knows). The program introduces days of the week, which is extraneous, but okay. A bigger issue is that it accepts \"possible_dates\" as a parameter, but then ignores them and defines \"month_days\" based on the original dates.]*"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.8.15"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/ipynb/Triplets.ipynb b/ipynb/Triplets.ipynb
new file mode 100644
index 0000000..7d8866b
--- /dev/null
+++ b/ipynb/Triplets.ipynb
@@ -0,0 +1,180 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "15a565d8-b9ee-427d-8631-6e1a26089b7f",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(0, 4, 7, 5, 2, 6, 1, 3)"
+ ]
+ },
+ "execution_count": 7,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "from itertools import permutations\n",
+ "from typing import *\n",
+ "\n",
+ "def nqueens(n=8) -> Iterable[Sequence[int]]:\n",
+ " \"\"\"All ways of arranging `n` non-attacking queens on an `n` x `n` board.\n",
+ " Each way is a sequence of `n` column numbers, one for each row\"\"\"\n",
+ " return (cols for cols in permutations(range(n))\n",
+ " if different(diagonal1(cols)) \n",
+ " and different(diagonal2(cols)))\n",
+ "\n",
+ "def different(items) -> bool: return len(items) == len(set(items))\n",
+ "def diagonal1(cols): return [col - row for row, col in enumerate(cols)]\n",
+ "def diagonal2(cols): return [col + row for row, col in enumerate(cols)]\n",
+ "\n",
+ "assert len(set(nqueens(8))) == 92\n",
+ "\n",
+ "next(nqueens(8))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "id": "fd7a971c-d3f4-4f2e-89db-777fb2a208d4",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Q . . . . . . . \n",
+ ". . . . Q . . . \n",
+ ". . . . . . . Q \n",
+ ". . . . . Q . . \n",
+ ". . Q . . . . . \n",
+ ". . . . . . Q . \n",
+ ". Q . . . . . . \n",
+ ". . . Q . . . . \n"
+ ]
+ }
+ ],
+ "source": [
+ "def show(queens, dot='. ', Q='Q ') -> None:\n",
+ " \"\"\"Print the board.\"\"\"\n",
+ " m = max(queens)\n",
+ " for col in queens:\n",
+ " print(dot * col + Q + dot * (m - col))\n",
+ "\n",
+ "show(next(nqueens())) "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "f8a27ed0-c2b1-47a0-bdf0-c6a8cd789dc5",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[(1, 2, 54),\n",
+ " (1, 3, 36),\n",
+ " (1, 4, 27),\n",
+ " (1, 6, 18),\n",
+ " (1, 9, 12),\n",
+ " (2, 3, 18),\n",
+ " (2, 6, 9),\n",
+ " (3, 4, 9)]"
+ ]
+ },
+ "execution_count": 6,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "import math\n",
+ "from itertools import combinations\n",
+ "\n",
+ "def find_product(n, k) -> List[Tuple[int, ...]]:\n",
+ " \"\"\"A list of all ways in which `k` distinct positive integers have a product of `n`.\"\"\" \n",
+ " factors = {i for i in range(1, n + 1) if n % i == 0}\n",
+ " return [tup for tup in combinations(factors, k) if math.prod(tup) == n]\n",
+ "\n",
+ "find_product(108, 3)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "c248a136-5ed5-40dd-ad48-b303e99c3675",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Triplets with product 108:\n",
+ "(1, 2, 54)\n",
+ "(1, 3, 36)\n",
+ "(1, 4, 27)\n",
+ "(1, 6, 18)\n",
+ "(1, 9, 12)\n",
+ "(2, 3, 18)\n",
+ "(2, 6, 9)\n",
+ "(3, 4, 9)\n"
+ ]
+ }
+ ],
+ "source": [
+ "def find_triplets(target_product):\n",
+ " triplets = []\n",
+ " for a in range(1, target_product + 1):\n",
+ " for b in range(a + 1, target_product + 1): # Ensure distinctness (b > a)\n",
+ " c = target_product // (a * b)\n",
+ " if a * b * c == target_product and c > b: # Ensure distinctness (c > b)\n",
+ " triplets.append((a, b, c))\n",
+ " return triplets\n",
+ "\n",
+ "target_product = 108\n",
+ "triplets = find_triplets(target_product)\n",
+ "\n",
+ "if triplets:\n",
+ " print(f\"Triplets with product {target_product}:\")\n",
+ " for triplet in triplets:\n",
+ " print(triplet)\n",
+ "else:\n",
+ " print(f\"No triplets found with product {target_product}\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6db0bbe1-5156-4e05-9a02-bbe3ed156f89",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.8.15"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}