{"nbformat_minor": 0, "cells": [{"source": "# Murdering Cheryl\n### How to generate and solve logical deduction problems", "cell_type": "markdown", "metadata": {}}, {"source": "You might be familiar with [Cheryl's Birthday](https://en.wikipedia.org/wiki/Cheryl's_Birthday), a mathematical brainteaser that went viral in 2015.\n\nHere's the text of the brainteaser:\n\n> Albert and Bernard just became friends with Cheryl, and they want to know when her birthday is. Cheryl gives them a list of 10 possible dates:\n \n> May 15 16 19\n> June 17 18\n> July 14 16\n> August 14 15 17\n\n> Cheryl then tells Albert and Bernard separately the month and the day of her birthday respectively.\n\n> **Albert:** I don't know when Cheryl's birthday is, but I know that Bernard doesn't know too.\n\n> **Bernard:** At first I didn't know when Cheryl's birthday is, but I know now.\n\n> **Albert:** Then I also know when Cheryl's birthday is.\n\n> So, when is Cheryl's birthday?\n\nIt is possible to arrive at the solution by systematically eliminating all but one of the possible dates using the information Albert and Bernard have provided.\n\nOne of the difficult things about this problem is that it requires you to maintain a mental model about what each person does and doesn't know (as well as what he could and couldn't know) at any point in time.\n\nIn this post, I'm going to present a new twist on the Cheryl's Birthday problem: a murder mystery.\n\nFirst, I'll give you the problem so you can try to solve it yourself. Next, I'll describe how I used code to generate the problem. Then, I'll show you how I used code to solve the problem. Finally, I'll offer some statistics about how often the generated puzzles result in interesting (or solvable) scenarios.\n", "cell_type": "markdown", "metadata": {}}, {"source": "## Cheryl's Murder\n\nWhen Cheryl grew up, she became a remarkably successful venture capitalist. To celebrate her 40th birthday, she invited 13 of her closest friends to spend the weekend at her beachfront mansion. Saturday evening, while her guests were partying on the first floor, Cheryl excused herself and walked to her room on the second floor. A few minutes later, her three maids, who were in different rooms upstairs, each heard Cheryl scream.\n\nThe first maid, Zoey, got to Cheryl's room and found Cheryl on the floor, stabbed in the stomach. Cheryl whispered a name to her, and then Zoey ran to get help. The second maid, Yvonne, ran into the room next and found Cheryl. Cheryl whispered something to her -- an article of clothing -- and then Yvonne, too, ran to get help. Finally, the third maid, Ximena, ran into the room. To Ximena, Cheryl was able to whisper one final word -- an occupation -- and then she breathed her last.\n\nAn hour later, Detective Kenneth Kong arrived at the mansion and interviewed the three maids. They explained that Cheryl had whispered a word to each of them, and it appeared that the words pointed to the party guest who stabbed her.\n\nHere is the list of guests who attended Cheryl's birthday party:\n\n* Albert, a dancer, who wore a ball cap\n* Albert, an artist, who wore a ball cap\n* Albert, an engineer, who wore an earring\n* Bernard, a bricklayer, who wore a ball cap\n* Bernard, an artist, who wore cuff links\n* Bernard, a dancer, who wore dungarees\n* Charles, a dancer, who wore an earring\n* Charles, a chef, who wore an ascot\n* Daniel, an engineer, who wore an ascot\n* Daniel, an artist, who wore an ascot\n* Daniel, a bricklayer, who wore an ascot\n* Daniel, an artist, who wore cuff links\n* Edward, an engineer, who wore an earring\n\nAnd here is the transcript of Detective Kong's interview with the three maids:\n\n> **Detective:** So ... what can you tell me? \n\n> **Zoey**: I don't know who did it.\n\n> **Yyvonne**: I don't know who did it.\n\n> **Ximena**: I don't know who did it.\n\n> **Zoey:** I still don't know who did it.\n\n> **Yyvonne:** I still don't know who did it.\n\n> **Ximena**: I still don't know who did it.\n\n> **Zoey:** I still don't know who did it.\n\n> **Yyvonne:** I know who did it!\n\n> **Ximena:** I know who did it!\n\n> **Zoey:** I know who did it!\n\nDetective Kong didn't wait for them to tell him the name of the person they suspected. He raced into the ballroom, where the 13 party guests were gathered, and arrested the culprit.\n\nSo ... whom did the detective arrest?", "cell_type": "markdown", "metadata": {}}, {"source": "## Generating the guest list\n\nWe've got five different names, five articles of clothing, and five occupations. That's 125 possibilities. But it's probably overkill to use all 125. It should be sufficient for our purposes to use about a tenth of them.\n\nHere's some code that will construct a guest list of roughly a dozen people with various combinations of the three attributes. It will then randomly select one of the people from the list to be the guilty party.", "cell_type": "markdown", "metadata": {}}, {"execution_count": 116, "cell_type": "code", "source": "from __future__ import division\nimport matplotlib.pyplot as plt\nfrom collections import defaultdict \nimport pandas as pd\nimport numpy as np\n\npd.set_option('display.notebook_repr_html', True)", "outputs": [], "metadata": {"collapsed": false, "trusted": true}}, {"execution_count": 117, "cell_type": "code", "source": "# for reproducibility\nSEED = 754687\nnp.random.seed(SEED)\n\n\n# Designate various possible attributes the suspects might have\nattributes = [\n {'key': 'name', 'values': ['Albert', 'Bernard', 'Charles', 'Daniel', 'Edward'] },\n {'key': 'clothing', 'values': ['Ascot', 'Ballcap', 'Cuff links', 'Dungarees', 'Earring'] },\n {'key': 'occupation', 'values': ['Artist', 'Bricklayer', 'Chef', 'Dancer', 'Engineer'] }, \n]\n\n\ndef get_suspects_dataframe(suspects=False):\n \"\"\"\n Construct a list of suspects. If the size of the attribute with the most values is N,\n then the list length will be roughly N multipled by a random number between 1 and N-1.\n \"\"\"\n\n if not suspects:\n # Sort the attributes so that the attribute with the most values is first.\n attributes.sort(key=lambda x: len(x['values']), reverse=True)\n\n # Construct the list of suspects by choosing one value from each attribute.\n largest_len = len(attributes[0]['values'])\n suspects = [{attr['key']: np.random.choice(attr['values']) for attr in attributes}\n for _ in range(largest_len * np.random.randint(1, largest_len))]\n \n # Create a dataframe from the suspects list, eliminating duplicates\n df = pd.DataFrame(suspects)\n df = df.drop_duplicates()\n return df\n \n \ndef get_guilty_attributes(df):\n \"\"\"\n Randomly select the guilty person.\n \"\"\"\n guilty = np.random.choice(df.index)\n guilty_attributes = df.ix[guilty].to_dict()\n return guilty_attributes\n\n\ndf = get_suspects_dataframe()\ndf = df.sort_values(by=['name', 'clothing'], ascending=[True, True])\ndf = df.reset_index(drop=True)\nguilty_attributes = get_guilty_attributes(df) # let's not reveal this yet\ndf", "outputs": [{"execution_count": 117, "output_type": "execute_result", "data": {"text/plain": " clothing name occupation\n0 Ascot Albert Dancer\n1 Dungarees Albert Engineer\n2 Earring Albert Chef\n3 Cuff links Bernard Bricklayer\n4 Cuff links Bernard Artist\n5 Earring Bernard Bricklayer\n6 Ascot Charles Engineer\n7 Ascot Charles Dancer\n8 Ballcap Charles Dancer\n9 Ballcap Edward Bricklayer", "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
clothingnameoccupation
0AscotAlbertDancer
1DungareesAlbertEngineer
2EarringAlbertChef
3Cuff linksBernardBricklayer
4Cuff linksBernardArtist
5EarringBernardBricklayer
6AscotCharlesEngineer
7AscotCharlesDancer
8BallcapCharlesDancer
9BallcapEdwardBricklayer
\n
"}, "metadata": {}}], "metadata": {"collapsed": false, "trusted": true}}, {"source": "## Solving the puzzle\n\nWe need to be able to keep track of what each of the three maids knows at any given time.\n\nThe code below constructs a `confidants` object that maintains what they know about the suspects.\n\nSpecifically, for each attribute (name, clothing, and occupation), we maintain what each maid thinks are the possible values they could be. At the start, each maid knows for certain one of the attributes -- Zoey knows the name, Yvonne knows the clothing, and Ximena knows the occupation -- but they may recognize that several values are possible for each of the other attributes.\n\nWhen a maid has whittled down the possibilities so that all three attributes have only one possible value, she has solved the mystery and knows the identity of the murderer.\n", "cell_type": "markdown", "metadata": {"collapsed": false}}, {"execution_count": 118, "cell_type": "code", "source": "confidant_names = ['Zoey', 'Yvonne', 'Ximena']\n\n# The confidants object keeps track of what each confidant knows about\n# the possible suspects. For each confidant, we track which values\n# that confidant considers viable possibilities for each attribute.\nconfidants = {}\n\ndef knows_guilty(confidant):\n \"\"\"\n Returns whether the given confidant knows who the guilty party is.\n \"\"\"\n for attr in attributes:\n attr_name = attr['key']\n if len(confidant[attr_name]) > 1:\n return False\n return True\n \ndef initialize_confidants(df, guilty_attributes):\n \"\"\"\n Each confidant knows one attribute associated with the guilty party,\n but does not necessarily know any others. We establish what each\n confidant knows about each attribute as a list of possible values.\n \"\"\"\n \n for index, attribute in enumerate(attributes):\n confidant_name = confidant_names[index]\n confidants[confidant_name] = {}\n confidant = confidants[confidant_name]\n attribute_name = attribute['key'] \n for attr in attributes:\n attr_name = attr['key']\n if (attribute_name == attr_name):\n # The confidant knows that this attribute is associated with the guilty party.\n confidant[attr_name] = [guilty_attributes[attr_name]]\n else:\n # The list of possible values for this attribute, given what the confidant knows.\n # At the start, she knows it could be any of the values associated with the\n # guilty attribute she does know. So, if she knows the guilty person's name is\n # Bernard, and that there were three Bernards present, one who wore a Ballcap\n # and two who wore Dungarees, then the confidant's list of clothing possibilities\n # is two: ['Ballcap', 'Dungarees'].\n confidant[attr_name] = df.loc[df[attribute_name] == guilty_attributes[attribute_name]][attr_name].drop_duplicates().values.tolist()\n\n # Add a 'knows_guilty' row to the dataframe\n for confidant_name in confidants:\n confidant = confidants[confidant_name]\n # It's possible, but not especially likely, that a confidant knows\n # the guilty party right from the start. For instance, if there's\n # only one person at the party named Edward, and the confidant knows\n # the guilty party is named Edward, then she knows his identity.\n confidant['knows_guilty'] = knows_guilty(confidant)\n \ninitialize_confidants(df, guilty_attributes) \n\nconfidants_df = pd.DataFrame(confidants)\nconfidants_df", "outputs": [{"execution_count": 118, "output_type": "execute_result", "data": {"text/plain": " Ximena Yvonne Zoey\nclothing [Dungarees, Ascot] [Ascot] [Ascot, Ballcap]\nknows_guilty False False False\nname [Albert, Charles] [Albert, Charles] [Charles]\noccupation [Engineer] [Dancer, Engineer] [Engineer, Dancer]", "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
XimenaYvonneZoey
clothing[Dungarees, Ascot][Ascot][Ascot, Ballcap]
knows_guiltyFalseFalseFalse
name[Albert, Charles][Albert, Charles][Charles]
occupation[Engineer][Dancer, Engineer][Engineer, Dancer]
\n
"}, "metadata": {}}], "metadata": {"collapsed": false, "trusted": true}}, {"source": "## Generating the transcript\n\nNow, we iterate over the maids, giving each a turn to say whether she knows who the murderer is.\n\nThe maids are able to rely on what was said previously to make inferences.\n", "cell_type": "markdown", "metadata": {}}, {"execution_count": 119, "cell_type": "code", "source": "def generate_transcript(df, **kwargs):\n \"\"\"\n Print a transcript of the maids' conversation.\n \"\"\"\n \n iterations = 0\n solved_in = 0\n \n # The confidants now take turns declaring whether they know\n # who the guilty party is. On the basis of what previous\n # confidants have disclosed, they are able to whittle down\n # the suspects.\n while iterations < 15:\n iterations += 1\n current_name, current_index, current_attribute, current_confidant = get_current_state(iterations)\n print \"{} (who knows the guilty person's {}) says:\".format(current_name, current_attribute)\n df, report = generate_statements(df, iterations, kwargs)\n if report.iloc[0]['solved_in']:\n break\n return df, report\n \n \ndef get_current_state(iterations):\n \"\"\"\n Get the current state as we cycle through the maids.\n \"\"\"\n current_name = confidant_names[iterations % len(confidant_names) - 1]\n current_index = confidant_names.index(current_name)\n current_attribute = attributes[current_index]['key']\n current_confidant = confidants[current_name]\n return current_name, current_index, current_attribute, current_confidant \n \n \ndef generate_statements(df, iterations, kwargs=None): \n \"\"\"\n Generate the statements a maid makes while it's her turn to talk.\n \"\"\"\n\n current_name, current_index, current_attribute, current_confidant = get_current_state(iterations) \n\n if not current_confidant['knows_guilty']:\n confidant_attributes = defaultdict(lambda: False)\n\n # We'll get to this later.\n df, current_confidant = set_could_know_guilty(df, iterations)\n\n # Whittle down the suspects list based on the information the other maids have shared.\n df, confidant_attributes = evaluate_claims(df, iterations, confidant_attributes)\n\n # We can now update what this confidant knows about\n # each attribute's possible values.\n current_confidant = update_attributes(df, iterations, confidant_attributes)\n\n if knows_guilty(current_confidant):\n current_confidant['knows_guilty'] = True\n print ' I know who did it!'\n else:\n current_confidant['knows_guilty'] = False\n print \" I don't know who did it.\"\n\n # We'll talk about this part later.\n check_could_know_guilty = kwargs.get('check_could_know_guilty', None)\n if check_could_know_guilty:\n check_could_know_guilty(df, confidants, current_name, current_index, current_attribute)\n\n num_knows_guilty = [True for confidant_name in confidants if knows_guilty(confidants[confidant_name])]\n if len(num_knows_guilty) == len(confidants):\n # Everybody has figured out the guilty party,\n # so no need to iterate further.\n # assert current_confidant[attr_name][0] == guilty_attributes[attr_name]\n solved_in = iterations \n return df, pd.DataFrame({'solved_in': solved_in, 'num_knows_guilty': len(num_knows_guilty)}, index=[0])\n \n return df, pd.DataFrame({'solved_in': None, 'num_knows_guilty': len(num_knows_guilty)}, index=[0])\n \n\ndef set_could_know_guilty(df, iterations):\n \"\"\"\n Determine whether the confidant could know the guilty party.\n \"\"\"\n current_name, current_index, current_attribute, current_confidant = get_current_state(iterations)\n \n if 'could_know_guilty' in current_confidant and current_confidant['could_know_guilty'] == False and len(df) > 2:\n discards = df.groupby(current_attribute)[current_attribute].transform(len) == 1\n discard_rows = df[discards]\n prev_attribute = attributes[current_index - 1]['key']\n attr_matches = discard_rows[prev_attribute].drop_duplicates().values.tolist()\n keep_attr = ~df[prev_attribute].isin(attr_matches)\n keep_attr_rows = df[keep_attr]\n if len(keep_attr_rows):\n df = keep_attr_rows\n current_confidant['could_know_guilty'] == None\n return df, current_confidant\n \n \ndef evaluate_claims(df, iterations, confidant_attributes):\n \"\"\"\n Use the information the other maids have shared to whittle down the suspects list.\n \"\"\"\n \n current_name, current_index, current_attribute, current_confidant = get_current_state(iterations)\n\n for confidant_name in confidants:\n confidant_index = confidant_names.index(confidant_name)\n if confidant_name != current_name and iterations > confidant_index:\n # We only want to evaluate the claims of the confidant who immediately\n # preceded this one, because the claims of other confidants may not\n # have had access to the knowledge the preceding confidant had.\n confidant_attribute = attributes[confidant_index]['key']\n if confidant_names[confidant_index] == confidant_names[current_index - 1] and len(df) > 2 and \\\n len(df) != len(df[df.groupby(confidant_attribute)[confidant_attribute].transform(len) > 1]):\n \n df, confidant_attributes = print_reasoning(\n df, iterations, confidant_attributes, confidant_name, confidant_attribute)\n \n return df, confidant_attributes\n\n\ndef print_reasoning(df, iterations, confidant_attributes, confidant_name, confidant_attribute):\n \"\"\"\n Output the maid's reasoning.\n \"\"\"\n\n confidant = confidants[confidant_name]\n \n current_name, current_index, current_attribute, current_confidant = get_current_state(iterations)\n\n if not confidant['knows_guilty']:\n print \" {} doesn't know who did it.\".format(confidant_name)\n keep_rows = df.groupby(confidant_attribute)[confidant_attribute].transform(len) > 1\n discard_rows = df[~keep_rows]\n for index, discard_row in discard_rows.iterrows():\n discard_dict = discard_row.to_dict()\n print \" If {} had been told the {} were {}, {} would know who did it.\".format(confidant_name, confidant_attribute, discard_dict[confidant_attribute], confidant_name)\n print \" Therefore, we can eliminate {} from the suspects list.\".format(\"-\".join(discard_dict.values()))\n df = df[keep_rows]\n else:\n discard_rows = df.groupby(confidant_attribute)[confidant_attribute].transform(len) > 1\n df = df[~discard_rows]\n possible_rows = df[current_attribute].isin(current_confidant[current_attribute])\n df = df[possible_rows]\n\n confidant_attributes[confidant_attribute] = df[confidant_attribute].values.tolist()\n print \" {} knows who did it.\".format(confidant_name)\n for index, guilty_row in df.iterrows():\n guilty_dict = guilty_row.to_dict() \n print \" If {} knows who did it, there can be only one suspect who matches the {} {} has been given.\".format(confidant_name, confidant_attribute, confidant_name)\n print \" There is only one suspect who matches {}.\".format(guilty_dict[confidant_attribute])\n print \" That suspect is {}.\".format(\"-\".join(guilty_dict.values()))\n if len(df) == 1:\n for attribute in attributes:\n attribute_name = attribute['key']\n confidant_attributes[attribute_name] = guilty_dict[attribute_name]\n \n return df, confidant_attributes\n\n \ndef update_attributes(df, iterations, confidant_attributes):\n \"\"\"\n Update what the current confidant knows about the guilty party's attributes.\n \"\"\"\n\n current_name, current_index, current_attribute, current_confidant = get_current_state(iterations) \n \n for attribute in attributes: \n attribute_name = attribute['key']\n if len(current_confidant[attribute_name]) > 1:\n if confidant_attributes[attribute_name]:\n if isinstance(confidant_attributes[attribute_name], list):\n current_confidant[attribute_name] = confidant_attributes[attribute_name]\n else:\n current_confidant[attribute_name] = [confidant_attributes[attribute_name]]\n else:\n df_guilty = df.loc[df[current_attribute] == guilty_attributes[current_attribute]][attribute_name].drop_duplicates().values.tolist()\n if len(df_guilty):\n current_confidant[attribute_name] = df_guilty\n return current_confidant\n \n \ngenerate_transcript(df) ", "outputs": [{"output_type": "stream", "name": "stdout", "text": "Zoey (who knows the guilty person's name) says:\n I don't know who did it.\nYvonne (who knows the guilty person's clothing) says:\n Zoey doesn't know who did it.\n If Zoey had been told the name were Edward, Zoey would know who did it.\n Therefore, we can eliminate Ballcap-Edward-Bricklayer from the suspects list.\n I don't know who did it.\nXimena (who knows the guilty person's occupation) says:\n Yvonne doesn't know who did it.\n If Yvonne had been told the clothing were Dungarees, Yvonne would know who did it.\n Therefore, we can eliminate Dungarees-Albert-Engineer from the suspects list.\n If Yvonne had been told the clothing were Ballcap, Yvonne would know who did it.\n Therefore, we can eliminate Ballcap-Charles-Dancer from the suspects list.\n I know who did it!\nZoey (who knows the guilty person's name) says:\n Ximena knows who did it.\n If Ximena knows who did it, there can be only one suspect who matches the occupation Ximena has been given.\n There is only one suspect who matches Engineer.\n That suspect is Ascot-Charles-Engineer.\n I know who did it!\nYvonne (who knows the guilty person's clothing) says:\n I know who did it!\n"}, {"execution_count": 119, "output_type": "execute_result", "data": {"text/plain": "( clothing name occupation\n 6 Ascot Charles Engineer, num_knows_guilty solved_in\n 0 3 5)"}, "metadata": {}}], "metadata": {"collapsed": false, "trusted": true}}, {"source": "## Results\n\nSometimes, the generated puzzles don't have a satisfying solution. The maids are unable to solve the mystery on the basis of what the others have said.\n\nI was curious about how often the puzzles terminate with a solution, and about how long it takes them to terminate.\n\nSo below is a script that runs the code above 1,000 times and outputs some statistics about the results.", "cell_type": "markdown", "metadata": {}}, {"execution_count": 120, "cell_type": "code", "source": "%%capture\n\nfound_guilty = []\nfull_results = []\n\ndef cheryls_murder():\n suspects_df = get_suspects_dataframe()\n guilty_attributes = get_guilty_attributes(suspects_df) \n confidants = {} \n initialize_confidants(suspects_df, guilty_attributes)\n df, report = generate_transcript(suspects_df)\n if report.iloc[0]['solved_in']:\n found_guilty.append(report)\n full_results.append(report)\n\nnum_iterations = 1000\nattempts = [cheryls_murder() for _ in range(num_iterations)]", "outputs": [], "metadata": {"collapsed": false, "trusted": true}}, {"source": "### Percentage solved", "cell_type": "markdown", "metadata": {}}, {"execution_count": 121, "cell_type": "code", "source": "percentage_solved = len(found_guilty) / num_iterations\nprint 'percentage solved: {}'.format(percentage_solved)", "outputs": [{"output_type": "stream", "name": "stdout", "text": "percentage solved: 0.166\n"}], "metadata": {"collapsed": false, "trusted": true}}, {"source": "### Number of confidants who know who's guilty", "cell_type": "markdown", "metadata": {}}, {"execution_count": 122, "cell_type": "code", "source": "%matplotlib inline\n\ndf_guilty = pd.concat(full_results)\ndf_guilty['num_knows_guilty'].hist()", "outputs": [{"execution_count": 122, "output_type": "execute_result", "data": {"text/plain": ""}, "metadata": {}}, {"output_type": "display_data", "data": {"image/png": "iVBORw0KGgoAAAANSUhEUgAAAXkAAAEACAYAAABWLgY0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAFy9JREFUeJzt3W2MXGd5xvG/iR1TQ1y/IAjYjRdV9EMhyAap2ALCDSUS\n2IW+uKiRpQbnS6uikBYqNbTlxajqp1agJEQUhZBEqA6QmJZGcpSQNE8bQVO1xDFx3dQJMTQ2aiCQ\nkkgRNYHth/NsZryZ9c7MzrX3mePrJ032OWfOmXOdfZx7j+89MwYzMzMzMzMzMzMzMzMzMzMzMzMz\nMxvgDuAQcAz4IvAi4Ebg4br+EPCauu0K4GrgKHA/sG2Zs5qZ2YjW9I1vBd4P3ABcNGDb3cDtdbwd\nOKyNZmZmZ/KCIbZ5pn5dBZwLfLcurxiw7U5gfx3fB6wFNi0loJmZjW+YIg9wEHgcOAUcqOuupWnL\nXAWsrus2Ayf79jtR15mZWYJhi/xO4BXAi4H3An9C04ffBrwM+LAknZmZLcnKEbb9MfAVml77TXXd\n/wGfB66oy/Ov3DfVdfOdpPmhYWZmwzkMPADsneSLrgU21vEq4DbgfcCGuu4FwGeAT9bl3TStHYAd\nLPyL19lJhmyhfdkBhPZlBxDblx1AbF92ALF92QGE9jFG7VzsSn498OW63c/R3DnzGZq7bLbQ/BA4\nRNO+gaZf/1aaXv2PgctGDdQRM9kBhGayA4jNZAcQm8kOIDaTHUBoZpydFivy3wFeP2D9b55hn8vH\nCWJmZt3R9XZNZAcQiuwAYpEdQCyyA4hFdgChYIpq59QENTNrkZFr57C3UNpoIjuAUGQHEIvsAGKR\nHUAssgMIxTg7ucibmdnEuV1jZjY6t2vMzKzHRV4jsgMIRXYAscgOIBbZAcQiO4BQjLPTKB9rMGEb\nHso7NsCzfwtP/UVuBjMzrUEfF7wcZptPIs5yD/DXd8MP3p4YwsxsVLOMWLcTr+TfkHfo0z4N2cys\nu9yT14jsAEKRHUAssgOIRXYAscgOIBTj7OQib2ZmEzcLs4mPA7Ow8a7sb4KZ2Yh8n7yZmfW4yGtE\ndgChyA4gFtkBxCI7gFhkBxCKcXZykTczs4lzT97MbHTuyZuZWY+LvEZkBxCK7ABikR1ALLIDiEV2\nAKEYZycXeTMzmzj35M3MRueevJmZ9bjIa0R2AKHIDiAW2QHEIjuAWGQHEIpxdhqmyN8BHAKOAV8E\nXgRsAL4KfBO4E1jXt/3VwFHgfmDbOKHMzGz5rOkb3wq8H7gGuLKu+xBwVR3vBm6v4+3A4QVe0z15\nM7PRSXryz9Svq4Bzge8CO4H9df3NwK463tW3/j5gLbBp1FBmZjYZw/bkDwKPA6eAA8Bmev/yxsm6\nDE1B7/8XOU70PXc2iewAQpEdQCyyA4hFdgCxyA4gFOPsNOy/DLUTeCHw98DecQ70fHuBmTpeB2yl\ndw6lflUtHwFOre8LM+kDbZ3w63nZy14+O5eDidXc4fwB8GngW8AFdd0W4JE6vh64tG/7RxncrnFP\n3sxsdBPvya8FNtbxKuCdwIM07Zs9df2eukz9ekkd7wCexv+gqplZa20BvkFzl8wxmrtoXsCZb6H8\nFL1bKF+3wOt2/Uo+hK+dLbIDiEV2ALHIDiAW2QGEgjGu5BfryX8HeP2A9T8ELl5gn8tHDWFmZt3S\n9St5MzMFyX3yZmY2pVzkNSI7gFBkBxCL7ABikR1ALLIDCMU4O7nIm5nZxLknb2Y2Ovfkzcysx0Ve\nI7IDCEV2ALHIDiAW2QHEIjuAUIyzk4u8mZlNnHvyZmajc0/ezMx6XOQ1IjuAUGQHEIvsAGKRHUAs\nsgMIxTg7ucibmdnEuSdvZjY69+TNzKzHRV4jsgMIRXYAscgOIBbZAcQiO4BQjLOTi7yZmU2ce/Jm\nZqNzT97MzHpc5DUiO4BQZAcQi+wAjZVP0Vy1JT5WPqU/z4mL7ABCMc5OLvJmrfTseZrafc8I2z57\nnv48ravckzc7s+T/R2bnKr21y8hz4it5M7MOc5HXiOwAQpEdQCyyA2iV7ABqkR1AKMbZabEifz7N\nn4oHgWPAvrr+RuBh4FB9vKauXwFcDRwF7ge2jRPKzMyWx0uAC+t4DfAQ8CbgBuCiAdvvBm6v4+3A\n4QVe1z15szNzT94GmXhP/gmaq3iAZ4AjwMvr8ooB2+8E9tfxfcBaYNOooczMbDJG6cnP0Fyd312X\nr6Vpy1wFrK7rNgMn+/Y5UdedbSI7gFBkBxCL7ABaJTuAWmQHEIpxdlo55HZrgC8BVwA/BK4EvkdT\n3G8CPgx8ZLRD76X5uQGwDthK7xxK/apaPgKcWt8XZtIH2jrh1/Py2bmcHOM5y31gL/eWg6ZYSp0D\n3AZ8cIHndwF31PH1wKV9zz3K4HaNe/JmZ+aevA0y8Z48wHXAceATfes29u3/bpq2DcBB4JI63gE8\nzentGzMzW0aLFfk30vxV4SJ6t0u+C/gszS2Sx4AN9G6tPEBz9X6Upmd/2aQDT4nIDiAU2QHEIjuA\nVskOoBbZAYRinJ0W68l/jcE/CG47wz6XjxPEzMy6wz15szNzT94GkfTkzcxsSrnIa0R2gNG04bPL\nW/P55ZEdQKtkB1CL7ABCMc5OLvLGaJ9dfs+Q243z8OeXm03aoI8mWA6zue2+LwO/dzf84O2JIdok\neT7mrHjuP9aGOfF8tNAsI86Jr+TNzDrMRV4jsgPolOwAapEdQKtkB1CL7ABCMc5OLvJmZh3mnrxB\n+nzMcQ+4TwvmxPPRQu7Jm5lZj4u8RmQH0CnZAdQiO4BWyQ6gFtkBhGKcnVzkzcw6zD15g/T5mOMe\ncJ8WzInno4Xckzczsx4XeY3IDqBTsgOoRXYArZIdQC2yAwjFODu5yJuZdZh78gbp8zHHPeA+LZgT\nz0cLuSdvZmY9LvIakR1Ap2QHUIvsAFolO4BaZAcQinF2cpE3M+sw9+QN0udjjnvAfVowJ56PFnJP\n3szMelzkNSI7gE7JDqAW2QG0SnYAtcgOIBTj7LRYkT+f5k/Fg8AxYF9dvwH4KvBN4E5gXd8+VwNH\ngfuBbeOEMjOz5fES4MI6XgM8BLwJuAa4sq7/EHBVHe8Gbq/j7cDhBV53FmYTHwdmYeNdE/9uTa/k\n+Zh7pDeh28TzYYOMPCeLXck/QXMVD/AMcAR4ObAT2F/X3wzsquNdfevvA9YCm0YNZWZmkzFKT36G\n5ur8bmAzcLKuP1mXoSnoJ/v2OdH33NkksgPolOwAapEdQKtkB1CL7ABCMc5OK4fcbg1wC3AF8MNx\nDvR8e2l+bkDT0t9K7xxK/apaPgKcWt8XZtIH2jrh11Mv12ELYuQcuK3LyTGes9wH9nJvOWiKpdQ5\nwG3AB/vWfQu4oI63AI/U8fXApX3bPcrgdo178u3Sgv6ve8DzeD5skIn35AGuA44Dn+hbdxDYU8d7\n6vLc+kvqeAfwNKe3b8zMrEXeCPwMeAA4VB+/xplvofwUvVsoX7fA63b9Sj6Er60wwvfunq5fOUZ2\ngEr0PR5l/loxH6OK7ABCwRhzslhP/mssfLV/8QLrLx81hJmZafizawzS52OOPyulTwvmxPPRQrP4\ns2vMzGyOi7xGZAfQKdkB1CI7gFbJDqAW2QGEYpydXOTNzDrMPXmD9PmY4x5wnxbMieejhdyTNzOz\nHhd5jcgOoFOyA6hFdgCtkh1ALbIDCMU4O7nIm5l1mHvyBunzMcc94D4tmBPPRwu5J29mZj0u8hqR\nHUCnZAdQi+wAWiU7gFpkBxCKcXZykTcz6zD35A3S52OOe8B9WjAnno8Wck/ezMx6XOQ1IjuATskO\noBbZAbRKdgC1yA4gFOPs5CJvZtZh7skbpM/HHPeA+7RgTjwfLTRyT36xfxnKzMxY+RQ8e15yhqfh\n2ZH3crtGI7ID6JTsAGqRHUCrZAdQC83LPntecxGd+Rjvh4yLvJlZh7nIa5TsADqRHUCtZAfQiuwA\naiU7QNu4yJuZdZiLvEZkB9Ap2QHUIjuAVskOoBbZAdpmmCL/OeBx4HjfuhuBh4FD9fGaun4FcDVw\nFLgf2DapoGZmpvFmmmLdX+RvAC4asO1u4PY63g4cXuA1Z2E28XFgFjbeNbHv0PRLno+5R/qN4W3i\n+WiXtszHyHMyzJX8vcCTA9YPuiF/J7C/ju8D1gKbRg1lZmaTsZSe/LU0bZmrgNV13WbgZN82J+q6\ns01kB9Ap2QHUIjuAVskOoBbZAdpm3He8Xgl8j6a43wR8GPjIaC+xF5ip43XAVnrzU+pX1fIR4NT6\nvjCTPtDWCb+eerkOWxAj58BtXU6O8ZzlPnBblxMOW2h+Bao3w+k9+X67gDvq+Hrg0r7nHmVwuya5\nt+We/Dwt6De6BzyP56Nd2jIfI8/JuO2ajX37v5umbQNwELikjncAT3N6+8bMzJbRMEX+FuDrNFfk\njwEfAD5Lc4vkMWADsK9ue4Dm6v0oTc/+ssnGnRqRHUCnZAdQi+wAWiU7gFpkB2ibYXry7xmw7pNn\n2P7yMbOYmdmE+R2vGiU7gE5kB1Ar2QG0IjuAWskO0DYu8mZmHeYirxHZAXRKdgC1yA6gVbIDqEV2\ngLZxkTcz6zAXeY2SHUAnsgOolewAWpEdQK1kB2gbF3kzsw5zkdeI7AA6JTuAWmQH0CrZAdQiO0Db\nuMibmXWYi7xGyQ6gE9kB1Ep2AK3IDqBWsgO0jYu8mVmHuchrRHYAnZIdQC2yA2iV7ABqkR2gbVzk\nzcw6zEVeo2QH0InsAGolO4BWZAdQK9kB2sZF3sysw1zkNSI7gE7JDqAW2QG0SnYAtcgO0DYu8mZm\nHeYir1GyA+hEdgC1kh1AK7IDqJXsAG3jIm9m1mEu8hqRHUCnZAdQi+wAWiU7gFpkB2gbF3kzsw5z\nkdco2QF0IjuAWskOoBXZAdRKdoC2cZE3M+swF3mNyA6gU7IDqEV2AK2SHUAtsgO0zTBF/nPA48Dx\nvnUbgK8C3wTuBNb1PXc1cBS4H9g2mZhmZjaOYYr8DcA75q37OHAX8FrgH+sywG7gVcAvA+8DbpxI\nyulTsgPoRHYAtZIdQCuyA6iV7ABtM0yRvxd4ct66ncD+Or4Z2FXHu/rW3wesBTYtMaOZmY1p3J78\nZuBkHZ+sy9AU9JN9253oe+5sEtkBdEp2ALXIDqBVsgOoRXaAtlmZd+i9wEwdrwO20pufUr+qlo8A\np9b3hZn0gbZO+PXUy3XYghg5B27rcnKM5yz3gdu6nHDYwnJ1vWc4/Rev3wIuqOMtwCN1fD1wad92\njzK4XTMLs4mPA7Ow8a4Jf4+mWfJ8zD2Yzf5GtIjno13aMh8jz8m47ZqDwJ463lOX59ZfUsc7gKc5\nvX1jZmbLaJgifwvwdZor8seAPwI+BvwqzS2UbwU+Wrc9QHP1fhS4FrhswnmnRWQH0CnZAdQiO4BW\nyQ6gFtkB2maYnvx7Flh/8QLrLx8zi5mZTZjf8apRsgPoRHYAtZIdQCuyA6iV7ABt4yJvZtZhLvIa\nkR1Ap2QHUIvsAFolO4BaZAdoGxd5M7MOc5HXKNkBdCI7gFrJDqAV2QHUSnaAtnGRNzPrMBd5jcgO\noFOyA6hFdgCtkh1ALbIDtI2LvJlZh7nIa5TsADqRHUCtZAfQiuwAaiU7QNu4yJuZdZiLvEZkB9Ap\n2QHUIjuAVskOoBbZAdrGRd7MrMNc5DVKdgCdyA6gVrIDaEV2ALWSHaBtXOTNzDrMRV4jsgPolOwA\napEdQKtkB1CL7ABt4yJvZtZhLvIaJTuATmQHUCvZAbQiO4BayQ7QNi7yZmYd5iKvEdkBdEp2ALXI\nDqBVsgOoRXaAtnGRNzPrMBd5jZIdQCeyA6iV7ABakR1ArWQHaBsXeTOzDnOR14jsADolO4BaZAfQ\nKtkB1CI7QNsstcifAg7Vx4G67pXAvwBHgC8Aq5Z4DDMzS3J8wLrbgN+p478BPjBgm1mYTXwcmIWN\nd8m+K9MneT7mHsxmfyNaxPPRLm2Zj5HnZNLtmpXAW4Bb6/LNwK4JH8PMzIa01CL/MuAbwL8Du4GX\nAj8CflqfPwlsXuIxplFkB9Ap2QHUIjuAVskOoBbZAdpm5RL33wJ8H3gV8M/02jRD2AvM1PE6YCu9\n+Sn1q2r5CHBqfV+YSR9o64RfT71chy2IkXPgti4nx3jOch+4rcsJhy3AjbTFl4DfBZ4Czqnr3gIM\n6n0n97bck5+nBf1G94Dn8Xy0S1vmY+Q5WUq75sXAuXX8MmA78CDwT8B76vo9wMElHMPMzJZgKUX+\nlcC/AoeBe4G/Ah4ArgD+kKYnsg64ZokZp1FkB9Ap2QHUIjuAVskOoBbZAdpmKT35B4FtA9YfB3Ys\n4XXNzGxC/I5XjZIdQCeyA6iV7ABakR1ArWQHaBsXeTOzDnOR14jsADolO4BaZAfQKtkB1CI7QNu4\nyJuZdZiLvEbJDqAT2QHUSnYArcgOoFayA7SNi7yZWYe5yGtEdgCdkh1ALbIDaJXsAGqRHaBtXOTN\nzDrMRV6jZAfQiewAaiU7gFZkB1Ar2QHaxkXezKzDXOQ1IjuATskOoBbZAbRKdgC1yA7QNi7yZmYd\n5iKvUbID6ER2ALWSHUArsgOolewAbeMib2bWYS7yGpEdQKdkB1CL7ABaJTuAWmQHaBsXeTOzDnOR\n1yjZAXQiO4BayQ6gFdkB1Ep2gLZxkTcz6zAXeY3IDqBTsgOoRXYArZIdQC2yA7SNi7yZWYe5yGuU\n7AA6kR1ArWQH0IrsAGolO0DbuMibmXWYqsi/AzgC/CdwpegYbRbZAXRKdgC1yA6gVbIDqEV2gLZR\nFPnVwHXATuC1wB5gm+A4bbY1O4DOA9kB1Do8d+D5O/soivwbgIeB/wZ+AnwZ2CU4Tputyw6g87/Z\nAdQ6PHfg+Tv7KIr8ZuBk3/KJus7MzJbZSsFrzg632dt+JDj2kL63Cn76E+EBZoSvnezb2QHUZrID\naH07O4DaTHaAtlkheM03Ax8H3laXPwY8C/xl3zaPAL8oOLaZWVcdpvmlyt7kHLyQpkWzBTiXJtjr\nUhOZmdlEvZPeLZR/mpzFzMzMzMxGsdgbolYDX6zbfJ2mvTNNFju/fcB3gEP1cfGyJVu6zwGPA8fP\nsM3VwFHgfqbvfRCLnd9e4H/ozd17lyfWxJxP866nB4FjNH8WB5nWORzm/PYyvXN4B03mYzQ18kUD\ntkmfu9XAY8AFwCqavvz8IH8MfLqOLwG+smzplm6Y8/sYcOky55qUN9Ocz0JFcDdwex1vpzn/abLY\n+b0X+OjyxZm4lwAX1vEa4CHgTfO2meY5HOb8pnkO1/SNbwXeP+/5keZO9bEGw7whaiewv44PAG9F\nc7ePwrBv+JqW85nvXuDJMzzfP3f3AWuBTepQE7TY+cH0zh3AEzRXuQDP0PyN8/x520zzHA5zfjC9\nc/hM/bqK5uaVk/OeH2nuVEV+mDdE9W/zE+BHwEtFeSZt2Dd8/TlNO+cmuvVOvLPhDW+XAf8B/B3w\nC8lZlmKG5mrv7nnruzKHMww+P5juOTxI01I8RXMR2W+kuVMV+SHfEDW1hjm/a4FfAl4NfB+4RprI\nJukfaN7H8Wqa3u/+M27dXmuALwFXsPjfXKbRmc5v2udwJ/AK4MUs8fcJqiJ/gtP/+rCZpoc9f5u5\nnz6rgJ+nKYbTYJjze6J+/RlwA/Ary5Brucy/cthU13XFk8BP6/izTOfcnUPzS7sv8PwrQZj+OVzs\n/Lowhz+m+V3l9nnrR5o7VZH/N5qr2Lk3RP0WvV8UzDlI8wmVAL9N89P2Z6I8kzbM+W3oG/86zW/C\nu+IgzS/LAXYAT/P8vuE065+732A65+46ml8sf2KB56d9Dhc7v2mdw7XAxjpeRXNF/+C8bVozd4Pe\nEPVx4F11vJrmr1pzt1DOLHO+pVrs/K6luQ3qv4A7ma6e4C3Ad2n6gY8BHwB+vz7mfIreLVzT9o7m\nxc7vz2jm7ijNn80LB7xGm72R5oLpAXq3EL6L7szhMOc3rXO4BfgGzR0zx4CraP7W0pW5MzMzMzMz\nMzMzMzMzMzMzMzMzMzMzMzMzMzMb7P8BfHNunONWTXsAAAAASUVORK5CYII=\n", "text/plain": ""}, "metadata": {}}], "metadata": {"collapsed": false, "trusted": true}}, {"source": "### Of those that were solved, number of steps it took to solve", "cell_type": "markdown", "metadata": {}}, {"execution_count": 123, "cell_type": "code", "source": "df_guilty.loc[df_guilty['solved_in'] != 0]['solved_in'].hist()", "outputs": [{"execution_count": 123, "output_type": "execute_result", "data": {"text/plain": ""}, "metadata": {}}, {"output_type": "display_data", "data": {"image/png": "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\nSZIkSZrH/h8/JLVsWv0DiAAAAABJRU5ErkJggg==\n", "text/plain": ""}, "metadata": {}}], "metadata": {"collapsed": false, "trusted": true}}, {"source": "## Cheryl's Birthday\n\nCan the same technique that solves the Cheryl's Murder mystery also solve the original Cheryl's Birthday problem?\n\nYes, but some modifications are needed.\n\nAt the beginning of the Cheryl's Birthday exchange, Albert says that 1) he himself doesn't know her birthday, and 2) Bernard couldn't possibly know her birthday either. So, he's making a statement not only about what he knows, but also about what he knows that someone else can know.\n\nHow does Albert know that Bernard could not know Cheryl's birthday? It's because he knows the correct month, and each of the day values associated with that month is associated with at least one other month. Thus, no matter what day Bernard has been told, he does not have enough information to figure out the correct month.\n\nTo work this into our `generate_transcript` function, we need to first determine whether each of the possible values a fellow confidant might have been given is an ambiguous value -- that is, it is associated with more than one suspect. If so, we can broadcast the fact that we know that the confidant could not know who the guilty party is. Then, upon receiving that information, we can work backward and eliminate any suspects whose values don't meet that condition.\n\nWe'll broadcast the information by adding a new attribute to each `confidant` object: `could_know_guilty`, which differs from `knows_guilty` because it indicates only the *possibility* that the person knows, not the certainty.\n", "cell_type": "markdown", "metadata": {}}, {"execution_count": 124, "cell_type": "code", "source": "attributes = [\n {'key': 'month', 'values': ['May', 'June', 'July', 'August'] },\n {'key': 'day', 'values': ['14', '15', '16', '17', '18', '19'] },\n]\n\nconfidant_names = ['Albert', 'Bernard']\n\nsuspects = [\n {'month': 'May', 'day': '15'},\n {'month': 'May', 'day': '16'},\n {'month': 'May', 'day': '19'},\n {'month': 'June', 'day': '17'},\n {'month': 'June', 'day': '18'},\n {'month': 'July', 'day': '14'},\n {'month': 'July', 'day': '16'},\n {'month': 'August', 'day': '14'},\n {'month': 'August', 'day': '15'},\n {'month': 'August', 'day': '17'},\n]\n\nconfidants = {}\n\ndf = get_suspects_dataframe(suspects)\nguilty_attributes = {'month': 'July', 'day': '16'}\ninitialize_confidants(df, guilty_attributes)\n\ndef check_could_know_guilty(df, confidants, current_name, current_index, current_attribute):\n for confidant_name in confidants:\n confidant_index = confidant_names.index(confidant_name)\n if confidant_names[confidant_index] == confidant_names[current_index - 1]:\n confidant_attribute = attributes[confidant_index]['key']\n confidant = confidants[confidant_name]\n ambiguous = df[df.groupby(confidant_attribute)[confidant_attribute].transform(len) > 1]\n if len(ambiguous):\n confidant_possible_values = ambiguous[confidant_attribute].drop_duplicates().values.tolist()\n possible_values = confidants[current_name][confidant_attribute]\n if set(possible_values).issubset(set(confidant_possible_values)):\n confidant['could_know_guilty'] = False\n print ' For all possible values that {} could be, there is more than one possible suspect.'.format(confidant_attribute)\n print ' Therefore, {} does not know who did it, either.'.format(confidant_name)\n\n \ngenerate_transcript(df, **{'check_could_know_guilty': check_could_know_guilty})\n", "outputs": [{"output_type": "stream", "name": "stdout", "text": "Albert (who knows the guilty person's month) says:\n I don't know who did it.\n For all possible values that day could be, there is more than one possible suspect.\n Therefore, Bernard does not know who did it, either.\nBernard (who knows the guilty person's day) says:\n I know who did it!\nAlbert (who knows the guilty person's month) says:\n Bernard knows who did it.\n If Bernard knows who did it, there can be only one suspect who matches the day Bernard has been given.\n There is only one suspect who matches 16.\n That suspect is 16-July.\n I know who did it!\n"}, {"execution_count": 124, "output_type": "execute_result", "data": {"text/plain": "( day month\n 6 16 July, num_knows_guilty solved_in\n 0 2 3)"}, "metadata": {}}], "metadata": {"collapsed": false, "trusted": true}}, {"source": "## More complex puzzles\n\nIt's possible to pass in an expanded attributes list to generate what is potentially a more complex puzzle:", "cell_type": "markdown", "metadata": {}}, {"execution_count": 125, "cell_type": "code", "source": "# for reproducibility\n# 786464\nSEED = 123480\nnp.random.seed(SEED)\n\n# for i in range(123456, 123556):\n# np.random.seed(i)\n\nattributes = [\n {'key': 'name', 'values': ['Albert', 'Bernard', 'Charles', 'Daniel', 'Edward', 'Francis', 'Gerald'] },\n {'key': 'clothing', 'values': ['Ascot', 'Ballcap', 'Cuff links', 'Dungarees', 'Earring'] },\n {'key': 'occupation', 'values': ['Artist', 'Bricklayer', 'Chef', 'Dancer', 'Engineer', 'Farmer'] }, \n {'key': 'favorite programming language', 'values': ['Assembly', 'BASIC', 'C', 'Dart'] }, \n]\n\ndf = get_suspects_dataframe()\n\nguilty_attributes = get_guilty_attributes(df)\n\ndf", "outputs": [{"execution_count": 125, "output_type": "execute_result", "data": {"text/plain": " clothing favorite programming language name occupation\n0 Earring C Gerald Dancer\n1 Cuff links C Gerald Engineer\n2 Ascot Assembly Charles Bricklayer\n3 Cuff links C Francis Artist\n4 Cuff links C Albert Farmer\n5 Cuff links Assembly Bernard Farmer\n6 Earring C Francis Bricklayer\n7 Earring BASIC Francis Chef\n8 Dungarees C Daniel Farmer\n9 Ascot Assembly Charles Artist\n10 Ascot Assembly Francis Artist\n11 Ascot BASIC Bernard Farmer\n12 Dungarees BASIC Francis Chef\n13 Earring Dart Bernard Chef\n14 Cuff links Dart Bernard Chef\n15 Ballcap BASIC Albert Chef\n16 Ballcap Dart Bernard Artist\n18 Ballcap BASIC Bernard Dancer\n19 Ballcap Assembly Edward Dancer\n20 Earring BASIC Francis Bricklayer", "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
clothingfavorite programming languagenameoccupation
0EarringCGeraldDancer
1Cuff linksCGeraldEngineer
2AscotAssemblyCharlesBricklayer
3Cuff linksCFrancisArtist
4Cuff linksCAlbertFarmer
5Cuff linksAssemblyBernardFarmer
6EarringCFrancisBricklayer
7EarringBASICFrancisChef
8DungareesCDanielFarmer
9AscotAssemblyCharlesArtist
10AscotAssemblyFrancisArtist
11AscotBASICBernardFarmer
12DungareesBASICFrancisChef
13EarringDartBernardChef
14Cuff linksDartBernardChef
15BallcapBASICAlbertChef
16BallcapDartBernardArtist
18BallcapBASICBernardDancer
19BallcapAssemblyEdwardDancer
20EarringBASICFrancisBricklayer
\n
"}, "metadata": {}}], "metadata": {"collapsed": false, "trusted": true}}, {"execution_count": 126, "cell_type": "code", "source": "confidant_names = ['Zoey', 'Yvonne', 'Ximena', 'Wendy'];\n\nconfidants = {}\n\ninitialize_confidants(df, guilty_attributes) \n\nconfidants_df = pd.DataFrame(confidants)\npd.set_option('display.notebook_repr_html', True)\nconfidants_df", "outputs": [{"execution_count": 126, "output_type": "execute_result", "data": {"text/plain": " Wendy \\\nclothing [Earring, Ascot, Dungarees, Ballcap] \nfavorite programming language [BASIC] \nknows_guilty False \nname [Francis, Bernard, Albert] \noccupation [Chef, Farmer, Dancer, Bricklayer] \n\n Ximena \\\nclothing [Ballcap] \nfavorite programming language [BASIC, Dart, Assembly] \nknows_guilty False \nname [Albert, Bernard, Edward] \noccupation [Chef, Artist, Dancer] \n\n Yvonne \\\nclothing [Earring, Ballcap] \nfavorite programming language [C, BASIC, Assembly] \nknows_guilty False \nname [Gerald, Bernard, Edward] \noccupation [Dancer] \n\n Zoey \nclothing [Cuff links, Ascot, Earring, Ballcap] \nfavorite programming language [Assembly, BASIC, Dart] \nknows_guilty False \nname [Bernard] \noccupation [Farmer, Chef, Artist, Dancer] ", "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
WendyXimenaYvonneZoey
clothing[Earring, Ascot, Dungarees, Ballcap][Ballcap][Earring, Ballcap][Cuff links, Ascot, Earring, Ballcap]
favorite programming language[BASIC][BASIC, Dart, Assembly][C, BASIC, Assembly][Assembly, BASIC, Dart]
knows_guiltyFalseFalseFalseFalse
name[Francis, Bernard, Albert][Albert, Bernard, Edward][Gerald, Bernard, Edward][Bernard]
occupation[Chef, Farmer, Dancer, Bricklayer][Chef, Artist, Dancer][Dancer][Farmer, Chef, Artist, Dancer]
\n
"}, "metadata": {}}], "metadata": {"collapsed": false, "trusted": true}}, {"execution_count": 127, "cell_type": "code", "source": "generate_transcript(df)", "outputs": [{"output_type": "stream", "name": "stdout", "text": "Zoey (who knows the guilty person's name) says:\n I don't know who did it.\nYvonne (who knows the guilty person's occupation) says:\n Zoey doesn't know who did it.\n If Zoey had been told the name were Daniel, Zoey would know who did it.\n Therefore, we can eliminate C-Dungarees-Daniel-Farmer from the suspects list.\n If Zoey had been told the name were Edward, Zoey would know who did it.\n Therefore, we can eliminate Assembly-Ballcap-Edward-Dancer from the suspects list.\n I don't know who did it.\nXimena (who knows the guilty person's clothing) says:\n Yvonne doesn't know who did it.\n If Yvonne had been told the occupation were Engineer, Yvonne would know who did it.\n Therefore, we can eliminate C-Cuff links-Gerald-Engineer from the suspects list.\n I don't know who did it.\nWendy (who knows the guilty person's favorite programming language) says:\n Ximena doesn't know who did it.\n If Ximena had been told the clothing were Dungarees, Ximena would know who did it.\n Therefore, we can eliminate BASIC-Dungarees-Francis-Chef from the suspects list.\n I don't know who did it.\nZoey (who knows the guilty person's name) says:\n I don't know who did it.\nYvonne (who knows the guilty person's occupation) says:\n Zoey doesn't know who did it.\n If Zoey had been told the name were Gerald, Zoey would know who did it.\n Therefore, we can eliminate C-Earring-Gerald-Dancer from the suspects list.\n I know who did it!\nXimena (who knows the guilty person's clothing) says:\n Yvonne knows who did it.\n If Yvonne knows who did it, there can be only one suspect who matches the occupation Yvonne has been given.\n There is only one suspect who matches Dancer.\n That suspect is BASIC-Ballcap-Bernard-Dancer.\n I know who did it!\nWendy (who knows the guilty person's favorite programming language) says:\n I know who did it!\nZoey (who knows the guilty person's name) says:\n I know who did it!\n"}, {"execution_count": 127, "output_type": "execute_result", "data": {"text/plain": "( clothing favorite programming language name occupation\n 18 Ballcap BASIC Bernard Dancer,\n num_knows_guilty solved_in\n 0 4 9)"}, "metadata": {}}], "metadata": {"collapsed": false, "trusted": true}}, {"source": "However, as you might expect, the more attributes and attribute values you use, the less likely the maids will be able to solve the puzzle.", "cell_type": "markdown", "metadata": {}}, {"source": "## Final thoughts\n\nThis exercise was born out of a Hack Days project at [Monetate](http://www.monetate.com). Twice a year, all of the software engineers at our company are given several days to work on a project of their choosing. It's a great way for us to try out new technologies and strengthen our programming chops. [Learn more about working at Monetate](http://www.monetate.com/about/careers/)\n\nThe code above uses two means of deduction to narrow down the suspects list.\n\nThe first assumes that if a confidant has stated that she does not know who the murderer is, we can rule out any of the remaining suspects who is the only person to have an attribute that the confidant knows about, because otherwise the confidant would be able to determine the murderer's identity based on that information, and thus would not have said that she does not know who the murderer is.\n\nThe second method is sort of the inverse of the first. It assumes that if a confidant has stated that she knows who the murderer is, then only the remaining suspects who are the only ones to have an attribute that the confidant knows about could be the murderer, and we can rule out everybody else.\n\nThere may very well be other methods to rule out certain suspects; if you know of any, I'd be interested to learn about them (and perhaps incorporate them into my puzzle-solving script).\n\nBy the way, in case you're wondering about the example puzzle that I used above to introduce the Cheryl's Murder mystery, the guest whom Detective Kong arrested was Bernard, a bricklayer, who wore a ball cap.\n\nCheck out the transcript below to see how his identity was deduced.\n", "cell_type": "markdown", "metadata": {"collapsed": false}}, {"execution_count": 128, "cell_type": "code", "source": "attributes = [\n {'key': 'name', 'values': ['Albert', 'Bernard', 'Charles', 'Daniel', 'Edward'] },\n {'key': 'clothing', 'values': ['Ascot', 'Ballcap', 'Cuff links', 'Dungarees', 'Earring'] },\n {'key': 'occupation', 'values': ['Artist', 'Bricklayer', 'Chef', 'Dancer', 'Engineer'] }, \n]\n\nsuspects = [{'clothing': 'Ballcap', 'name': 'Albert', 'occupation': 'Dancer'},\n {'clothing': 'Ballcap', 'name': 'Albert', 'occupation': 'Artist'},\n {'clothing': 'Earring', 'name': 'Albert', 'occupation': 'Engineer'},\n {'clothing': 'Ballcap', 'name': 'Bernard', 'occupation': 'Bricklayer'},\n {'clothing': 'Cuff links', 'name': 'Bernard', 'occupation': 'Artist'},\n {'clothing': 'Dungarees', 'name': 'Bernard', 'occupation': 'Dancer'},\n {'clothing': 'Earring', 'name': 'Charles', 'occupation': 'Dancer'},\n {'clothing': 'Ascot', 'name': 'Charles', 'occupation': 'Chef'},\n {'clothing': 'Ascot', 'name': 'Daniel', 'occupation': 'Engineer'},\n {'clothing': 'Ascot', 'name': 'Daniel', 'occupation': 'Artist'},\n {'clothing': 'Ascot', 'name': 'Daniel', 'occupation': 'Bricklayer'},\n {'clothing': 'Cuff links', 'name': 'Daniel', 'occupation': 'Artist'},\n {'clothing': 'Earring', 'name': 'Edward', 'occupation': 'Engineer'}]\n\ndf = get_suspects_dataframe(suspects=suspects)\n\nguilty_attributes = {'clothing': 'Ballcap', 'name': 'Bernard', 'occupation': 'Bricklayer'}\n\nconfidant_names = ['Zoey', 'Yvonne', 'Ximena']\n\nconfidants = {}\n\ninitialize_confidants(df, guilty_attributes)\n\ngenerate_transcript(df)", "outputs": [{"output_type": "stream", "name": "stdout", "text": "Zoey (who knows the guilty person's name) says:\n I don't know who did it.\nYvonne (who knows the guilty person's clothing) says:\n Zoey doesn't know who did it.\n If Zoey had been told the name were Edward, Zoey would know who did it.\n Therefore, we can eliminate Earring-Edward-Engineer from the suspects list.\n I don't know who did it.\nXimena (who knows the guilty person's occupation) says:\n Yvonne doesn't know who did it.\n If Yvonne had been told the clothing were Dungarees, Yvonne would know who did it.\n Therefore, we can eliminate Dungarees-Bernard-Dancer from the suspects list.\n I don't know who did it.\nZoey (who knows the guilty person's name) says:\n Ximena doesn't know who did it.\n If Ximena had been told the occupation were Chef, Ximena would know who did it.\n Therefore, we can eliminate Ascot-Charles-Chef from the suspects list.\n I don't know who did it.\nYvonne (who knows the guilty person's clothing) says:\n Zoey doesn't know who did it.\n If Zoey had been told the name were Charles, Zoey would know who did it.\n Therefore, we can eliminate Earring-Charles-Dancer from the suspects list.\n I don't know who did it.\nXimena (who knows the guilty person's occupation) says:\n Yvonne doesn't know who did it.\n If Yvonne had been told the clothing were Earring, Yvonne would know who did it.\n Therefore, we can eliminate Earring-Albert-Engineer from the suspects list.\n I don't know who did it.\nZoey (who knows the guilty person's name) says:\n Ximena doesn't know who did it.\n If Ximena had been told the occupation were Dancer, Ximena would know who did it.\n Therefore, we can eliminate Ballcap-Albert-Dancer from the suspects list.\n If Ximena had been told the occupation were Engineer, Ximena would know who did it.\n Therefore, we can eliminate Ascot-Daniel-Engineer from the suspects list.\n I don't know who did it.\nYvonne (who knows the guilty person's clothing) says:\n Zoey doesn't know who did it.\n If Zoey had been told the name were Albert, Zoey would know who did it.\n Therefore, we can eliminate Ballcap-Albert-Artist from the suspects list.\n I know who did it!\nXimena (who knows the guilty person's occupation) says:\n Yvonne knows who did it.\n If Yvonne knows who did it, there can be only one suspect who matches the clothing Yvonne has been given.\n There is only one suspect who matches Ballcap.\n That suspect is Ballcap-Bernard-Bricklayer.\n I know who did it!\nZoey (who knows the guilty person's name) says:\n I know who did it!\n"}, {"execution_count": 128, "output_type": "execute_result", "data": {"text/plain": "( clothing name occupation\n 3 Ballcap Bernard Bricklayer, num_knows_guilty solved_in\n 0 3 10)"}, "metadata": {}}], "metadata": {"collapsed": false, "trusted": true}}, {"source": "---\n*Shaun Gallagher is a software engineer at Monetate. He's also the author of two nonfiction books, [Experimenting With Babies: 50 Amazing Science Projects You Can Perform on Your Kid](http://www.experimentingwithbabies.com) and [Correlated: Surprising Connections Between Seemingly Unrelated Things](http://www.correlated.org), both published by Penguin Random House and available wherever awesome things are sold.*", "cell_type": "markdown", "metadata": {"collapsed": true}}], "nbformat": 4, "metadata": {"kernelspec": {"display_name": "Python 2", "name": "python2", "language": "python"}, "language_info": {"mimetype": "text/x-python", "nbconvert_exporter": "python", "version": "2.7.3", "name": "python", "file_extension": ".py", "pygments_lexer": "ipython2", "codemirror_mode": {"version": 2, "name": "ipython"}}}}