# Mini Cactpot Algorithm Part 2

07 Jan 2018 | programming

This is the second part of the mini cactpot algorithm. This post is about calculating potential values of each line on the ticket and recommend the highest line of value to the user. If you want to see how to set up the mini cactpot ticket, use this link: Part 1.

## Class Calculate

Similar to class `Game`, class `Calculate` will also take one argument, the dictionary of ticket positions. I’m going to initialize the class.

``````class Calculate:
def __init__(self, ticket):
self.positions = ticket
``````

Since I want to calculate each three number lines, I use dictionary to map each line to its three positions on the ticket.

``````def lines(self):
ticket_lines = {
1: [self.ticket['g'], self.ticket['h'], self.ticket['i']],
2: [self.ticket['d'], self.ticket['e'], self.ticket['f']],
3: [self.ticket['a'], self.ticket['b'], self.ticket['c']],
4: [self.ticket['a'], self.ticket['e'], self.ticket['i']],
5: [self.ticket['a'], self.ticket['d'], self.ticket['g']],
6: [self.ticket['b'], self.ticket['e'], self.ticket['h']],
7: [self.ticket['c'], self.ticket['f'], self.ticket['i']],
8: [self.ticket['c'], self.ticket['e'], self.ticket['g']]
}
return ticket_lines
``````

My next method, `combinations`, is actually a Python built-in. The reason why I wrote out the code was because my free Trinket.io did not support itertools.

``````@classmethod
def combinations(cls, iterable, r):
pool = tuple(iterable)
n = len(pool)
if r > n:
return
indices = list(range(r))
yield tuple(pool[i] for i in indices)
while True:
for i in reversed(range(r)):
if indices[i] != i + n - r:
break
else:
return
indices[i] += 1
for j in range(i+1, r):
indices[j] = indices[j-1] + 1
yield tuple(pool[i] for i in indices)
``````

The method takes an iterable and return tuples of combination for those elements at specific length. You can read more about it here: itertools.combinations. Suppose if you have a list of 4 items and you want to get all 2 item combinations, you can use `itertools.combinations(list, length)` for that.

``````import itertools

a = [1, 2, 3, 4]
b = list(itertools.combinations(a, 2))

>>> [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
``````

With this method, I can calculate possible combinations for the numbers the user has not picked. So if the user picked 1, 2, 3, 4 then 5, 6, 7, 8, 9 are still available. Consequently, I just need to find out which positions on the ticket that is still hidden and replace those with the available numbers. I received help from Francisco Couzo on StackOverflow on the method to replace hidden values with combinations. Here’s the link for the question I posted: question.

``````def lists_combinations(self, list_1, list_2):
indices = [i for i, x in enumerate(list_1) if isinstance(x, str)]
for combo in self.combinations(list_2, len(indices)):
for index, char in zip(indices, combo):
list_1[index] = char
yield tuple(list_1)
``````

His function finds the indices of all the string in the first list and replace them with combinations from second list. I modified the function to fit with the class. In my case, `list_1` is the value of each line for the dictionary I created earlier. While `list_2` is the available numbers.

## Calculating Payout

The actual calculation happens in the method `lines_combinations`. I used another dictionary to map each line to its possible combinations. It uses the method earlier to do that. `num_lines` and `num_list` are simply the same arguments for `list_combinations`.

``````def lines_combinations(self, num_lines, num_list):
lines_combo = {}
for line in num_lines:
lines_combo[line] = list(self.lists_combinations(num_lines[line], num_list))
return lines_combo
``````

With the combinations for each line calculated, I can go on crunching out the potential payout of each line. The payout varies depend on the sum for each line. By summing up each combinations returned from `lines_combination` and multiply them with the payout, I can get all potential payouts. Finally, I can find the average potential payout by using floor division of the total sum with the length of the list.

``````@classmethod
def lines_payout(cls, combinations):
payout = {6: 10000, 7: 36, 8: 720, 9: 360, 10: 80, 11: 252, 12: 108,
13: 72, 14: 54, 15: 180, 16: 72, 17: 180, 18: 119,
19: 36, 20: 306, 21: 1080, 22: 144, 23: 1800, 24: 3600}

sum_payout = {}
for line in combinations:
sum_payout[line] = [sum(combo) for combo in combinations[line]]

for line in sum_payout:
sum_payout[line] = sum([payout[value] for value in sum_payout[line]]) // len(sum_payout[line])
return sum_payout
``````

However, the user does not need to know what happens in the code. Therefore I used another method to print out a message notifying the user which line has the best possible payout. There are cases where multiple lines can have the best payout. `recommendation` takes the dictionary with each line average payout and find the maximum value. It returns a string containing the line(s) recommended and the potential payout value(s).

``````@classmethod
def recommendation(cls, user_payout):
print('---------------')
highest = max(user_payout.values())
recommend = [key for key, value in user_payout.items() if value == highest]
string = ''
for key in recommend:
string += Color.BOLD + '> Line {} has the highest payout of {}!\n'.format(key, user_payout[key]) + Color.END
return string
``````

All that’s left to do now is to write a function to set up the game and calculate the potential payout. That should be a simple matter so I’m going to end the post here.