Solving MTV’s Are You the One with Functional Scala
By: Laura Chapman
Tags: Are You The One, Scala, Functional Programming

(contains some season 1 spoilers)

I recently discovered the dating show Are You the One, which originally aired on MTV in 2014. The first two seasons are currently on Netflix. I decided to get nerdy about it, and this is my story. The “we” in this article refers to me and my computer, a 2015 MacBook Air on its last legs, and you as well if you clone my code and try it for yourself.

Are You the One describes itself as “the most ambitious matchmaking experiment ever attempted.” Out of ten straight men and women, the show has assigned ten “perfect matches,” which the group must figure out by the end of the show in order to win a million dollars (split 20 ways) (the largest prize in MTV history). Each episode, the group chooses one couple to send to the Truth Booth, where they find out if they are a perfect match or not. They also match themselves up at the end of each episode and are told how many couples are perfect matches, but not who those couples are.

Basically, this is a constraint satisfaction problem, kind of like the board game Mastermind. Out of 100 possible matches- 10 women, each with 10 possibilities for men- 10 matches are perfect, with each man and woman having precisely one perfect match. Every episode, information is gained from the Truth Booth and the matchup ceremonies, putting additional constraints on the possible solutions. A valid solution is a match assignment for which:

Each matchup ceremony has the correct number of matches

Each man is matched to one woman

No matches violate results from the Truth Booth.

As the season progresses, the number of valid solutions narrows. A few episodes in, I knew I wanted to try to solve this programmatically. I was curious about how much information could be deduced from the matchup ceremonies and Truth Booths, and had a feeling it was a lot.

Logic (Without Code)

We could generate all possible matchups and test each one to see if the three constraints above are satisfied. But as there are 10! (about 3.6 million) possible matchups, this would be inefficient. We can shortcut this by testing possibilities for the perfect matches in each matchup ceremony. This greatly narrows the number of possibilities to test. If there are two correct matches out of 10 in a matchup ceremony, we can suppose any two of those couples are valid, and see what that implies for other matchup ceremonies. There are 45 ways to pick two couples out of 10, so already this is a huge improvement from 10!. Every time we suppose a couple is correct, this narrows down the possibilities for future matchups.

For example, say we suppose Jim and Pam are a perfect match, based on the fact that they were matched together in a round with one or more correct matches. Then in future matchups, we know that any pairings of Pam with anyone other than Jim are incorrect, and likewise for Jim with anyone other than Pam. Additionally, if Jim and Pam are paired together in any future matchups, we know that this correct match contributed to the perfect match count, and this decreases the number of unknown perfect matches for that round. If we ever get to a matchup round in which there aren’t enough possibly correct couples left to meet the number of perfect matches, we know that there cannot be a solution for this proposed matchup. One or more of our assumed perfect matches must have been wrong, so we can abandon this solution, and move on to other possibilities.

We can continue this strategy for each round, testing all could-be-valid combinations of perfect matches and using that information to narrow the choices for future rounds. We end up with a set of consistent possible solutions that match up every man and woman in a way that produces the right number of matches for each matchup ceremony and complies with the Truth Booth.

Each solution represents a possible answer for which couples were the perfect matches in each matchup ceremony. To get the rest of the perfect match possibilties for the full group- including couples who were never matched up in a matchup ceremony- we can see who’s left, and generate all possible pairings that don’t violate Truth Booth conditions.

Once we have a set of solutions, we can see if there are any couples that are together in every solution. If so, these couples are perfect matches for sure- no need for the Truth Booth!

Code

This strategy lends itself to recursion, because we’re building up a solution based on different assumptions of perfect couples. We want to see what assignments of ten perfect matches are possible given some assumed perfect couples. Therefore, we’ll want a function that can generate possible solutions given some starting set of matches/non-matches. We’ll add more matches to the match set and non-matches to the non-match set, and then generate the possible solutions given that new set of matches, by running the same function again on new input. Classic recursion.

Functional programming is great for this problem because in functional programming, all variables are immutable. This makes it easy to test lots of possibilities recursively, because the program is never modifying variables that are used elsewhere. If we’re keeping track of a list of perfect couples, we can add different combinations of couples, and the functional program will make a copy of the list with the new couples, instead of changing the list in place. This makes it easy to track different possibilities.

For example, suppose we have Abby/Bob on our perfect match list, and we want to see if Pam/Jim or Marge/Homer might lead to a valid solution. With functional programming, we can add Pam/Jim to our match list to get (Abby/Bob, Pam/Jim). We can see if that solution works, and also test Marge/Homer by adding them to that same starting list to get (Abby/Bob, Marge/Homer).

With a mutable language like Python, we would have to explicitly make a copy of the list every time we wanted to test a new couple. Otherwise, when we add Pam/Jim, the list would change in place, and then when we test Marge/Homer, we’d be adding Marge/Homer to a list that already contains (Abby/Bob, Pam/Jim). This is no good, because we want to test if Pam/Jim or Marge/Homer lead to valid solutions, not both at once. We could use Python and just make sure we are making deep copies of the list every time we want to add to it, essentially simulating an immutable list. But this would be error-prone and harder to debug, so it’s better to use a language that is designed for variable immutability, such as Scala. Scala also has cleaner support for mapping and filtering than Python, which is useful in this project for filtering out invalid couples, mapping couples to single-gender lists, and more.

We can start by setting up the input. We have three files- the matchup ceremony information, the confirmed perfect matches, and the confirmed non-matches, from the Truth Booth. We could set up these files with the knowledge known at any given episode in the season. It’s important to follow a convention for the gender order of each line, here the men are first.

We read and format the input as follows.

The confirmed matches and not matches are represented as a set of a list of strings, and the matchup ceremony information is represented as a list of tuples containing an integer (the number correct) and a list of list of strings (the pairings). We also make a list of all the men and a list of all the women.

We can define a function solve that generates a list of solutions given matchup ceremony information and assumed matches/non-matches so far. Our solve function takes in a list of matchups, a set of assumed matches and a set of non-matches. It returns a list of solutions, with each solution a tuple containing a set of matches and a set of non-matches. To solve the overall problem, we can run solve with only Truth Booth-confirmed matches/nonmatches and all of the matchup ceremonies so far. Here is the function signature:

solve considers the matchup ceremonies one at a time. If there are no matchups left in the list, then we are done solving, and we just return the sets of matches and non-matches, wrapped in a list. This is the recursive base case for success, a solution that works.

If there are still matchups left to consider, we start by updating the matchups to reflect the current matches and non-matches. We first adjust according to the perfectly matched couples with adjustCouplesAndCounts , filtering those couples out of future matchups and decrementing the perfect match counts accordingly. Then we filter out any matches in the non-matches list (line 17 below), and matches that contain any of the men or women already matched up (line 18 below).

Now that we have the matchups updated, we look at the first matchup in that list and consider a few possibilities. The matchup contains a number and a list of matches from that ceremony.

If the number is 0, then there are no perfect matches that round, and we can just move on to the next matchup round by calling solve on the rest of the matchup list.

If the number equals the number of couples in the list, then all of those couples in that round must be perfect matches. We add these couples to the assumed list of matches, and move on to the next matchup round by calling solve on the rest of the matchup list (line 4 below). Note that the gist below contains just the cases in the updateMatchUps(matchUps, matches, notMatches) match clause, for brevity.

If the number is greater than the number of matches in the list, then there is a problem. There can’t be 4 perfect matches from 3 couples, for example. This means that one or more match assumptions made were wrong, and there can be no solution with these matches. We return an empty list in this case (lines 5–6 below). This is the recursive base case for failure, a solution that doesn’t work.

Otherwise, there is the most interesting case, where the number is greater than the number of couples in the list for the matchup, for example, when there are 2 matches out of 5 couples. In this case, we have to try different combinations of couples.

We generate all the combinations of perfect matches (each combination with the specified number of couples) in the matchup in the matchup list (line 12 below). For each combination of couples, we call solve with updated information:

Move on to the next round of matchups by passing in the tail of the matchup list (line 15 below).

Add the matches from that combination into the match list (line 16 below).

Add the other couples in that matchup ceremony to the list of non-matches (line 17 below). If any of these couples were also matches, it would throw off the count for the ceremony, so they can’t be matches.

We aggregate the results from all the combinations into a list by accumulating a list in a foldLeft .

That’s all we need to solve the problem if we just want to see the possibilities for perfect matches in each matchup ceremony.

But this won’t necessarily give us all the possibilities for the ten perfect matches, because there could be some perfect matches that have never shown up in the matchup ceremonies. If we run the program with just the information from episode one of season one, we’d only be predicting possibilities for two matches, the two who were matched up correctly in that episode. We might be curious how many total solutions are possible for all ten matchups, and what those solutions are.

So we can add a final step, which is to figure out what the possibilities for the rest of the matches are, at the point when we’ve exhausted the matchup ceremonies ( case Nil ). We can do this by getting all the men and women not already matched up, finding all the valid matchup combinations, and building a result list with each possibility. See the updated case Nil in lines 7–16 below:

If we were running the program early in the season, this extra speculative step would add a lot of possibilities, because we don’t know a ton of information yet from the Truth Booth or the matchup ceremonies. In this case, we might want to just see what the possibilities were for the perfect matches from the ceremonies, not see all the possibilities for all ten matches. We can support this flexibility by adding a boolean parameter speculate that controls whether or not we perform this last step- see lines 8–18 in the gist below:

Finally, we can write a few functions that print out information about our solutions. If a match shows up in every solution, it must be a perfect match no matter what. Likewise, if a couple shows up as not a match in every solution, that couple cannot be a perfect match. We can figure this out by reducing the matches with a set intersection.

We can also add a function to print out all the information of each solution: which couples were matches for that solution, which couples were not matches, and what the matches were for each round.

Finally, we can run our program and see the results. You can find my finished code here. The partial output below shows the three possible solutions after the sixth matchup ceremony of season one.

In season one, there is only one solution after the seventh matchup ceremony, though it took an additional episode for the contestants to win the challenge. This is unsurprising, given the difficulty of reasoning about systems with this many variables without a computer, which contestants aren’t allowed to have. So I wouldn’t be able to go on Are You the One and make an easy 50k with my solver program.

In later seasons, they banned pens and paper to keep the contestants from trying to solve it with math, though in season one they did have paper in the form of Ryan Malaty’s dream journal. In theory he could have run the match possibilities by hand, but I don’t blame him for not.

I’m not the first one to explore “computational MTV”- there’s an excellent blog called Are You the One? Math that goes through the match probabilities for every episode of all 8 seasons. Other blogs and articles exist as well with interesting analyses of the show.

Thank you for reading. Again, you can find my code here.