-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathticketgenerator.py
125 lines (101 loc) · 5.39 KB
/
ticketgenerator.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#! python
"""Housie Ticket generation logic"""
__author__ = 'Aaron Alphonso'
__email__ = '[email protected]'
from collections import defaultdict
from random import choice, randint
from typing import List, Set, Dict, DefaultDict
from housie.constants import Number, Row, ColumnRange, COLUMN_RANGES
from housie.models import Ticket
def generate_ticket() -> Ticket:
"""Generates a Housie Ticket containing 15 randomly selected numbers based on the following rules
* A Housie ticket has 15 numbers.
* These 15 numbers are placed in a 3 rows x 9 cols grid (27 possible spaces).
* There has to be exactly 5 numbers in each row.
* There has to be at least one number in each column.
* The numbers in each column must belong to that column's allowed range.
Column 1: 1-9
Column 2: 10-19
Column 3: 20-29
. . .
. . .
. . .
Column 8: 70-79
Column 9: 80-90
Based on the rules above we have to select the numbers.
"""
# The list of ranges for each column. Make a copy as we mutate it during our selection process
column_ranges: List[ColumnRange] = COLUMN_RANGES.copy()
# The numbers selected for the ticket
ticket_numbers: Set[Number] = set()
# Saving the numbers column-wise to ensure we select at max 3 numbers for each column and also
# to help in distributing them into the 3 rows later
column_wise_numbers: DefaultDict[ColumnRange, List[Number]] = defaultdict(list)
# Select one number from each column range. This will give us 9 numbers
for column_range in column_ranges:
selected_number = select_unique_number_from_range(
column_range=column_range, already_selected=column_wise_numbers[column_range])
ticket_numbers.add(selected_number)
column_wise_numbers[column_range].append(selected_number)
# Select the remaining 6 numbers at random from the columns.
while len(ticket_numbers) < 15:
selected_range = choice(column_ranges)
selected_number = select_unique_number_from_range(
column_range=selected_range, already_selected=column_wise_numbers[selected_range])
ticket_numbers.add(selected_number)
column_wise_numbers[selected_range].append(selected_number)
# If we have selected 3 numbers for a specific column, remove that column from the further selection process
if len(column_wise_numbers[selected_range]) == 3:
column_ranges.remove(selected_range)
# Convert to a regular dict now as we no longer need the defaultdict properties
# Now we assign the 15 selected numbers into the 3 rows.
rows = assign_to_rows(dict(column_wise_numbers))
return Ticket(rows)
def select_unique_number_from_range(column_range: ColumnRange, already_selected: List[Number]) -> Number:
"""Selects and returns a number randomly from a ColumnRange. If the number has already been selected before, then
re-select a new number"""
selected_number: Number = randint(column_range.start, column_range.end)
# If the selected_number is already_selected, select a new number until we get a unique number
while selected_number in already_selected:
selected_number = randint(column_range.start, column_range.end)
return selected_number
def assign_to_rows(column_wise_numbers: Dict[ColumnRange, List[Number]]) -> List[Row]:
"""Distribute the ticket numbers into the 3 rows such that each row has exactly 5 numbers
Start with the columns that have 3 numbers, followed by the columns that have two numbers and
then columns that have 1 number.
Assign the numbers in a round-robin fashion.
"""
ticket: List[Row] = [[], [], []]
row_num = 0
for col_len in [3, 2, 1]:
columns = list(filter(lambda x: len(x) == col_len, column_wise_numbers.values()))
for column in columns:
row_num = insert_from_columns_to_rows(column, ticket, row_num)
for row in ticket:
row.sort()
return ticket
def insert_from_columns_to_rows(column: List[Number], ticket: List[Row], row_num: int) -> int:
"""Insert numbers from the columns into the rows in a round-robin fashion.
Returns the row_num, so it can be persisted between calls"""
numbers = sorted(column)
# If we have to assign numbers to row 3 and row 1 of the same column, we should always assign the smaller
# number to the higher row, i.e. row 1. To do this, we have the following logic:
# Figure out which rows we will be assigning the column numbers to. Assign in round robin logic starting from the
# smallest row num possible.
row_nums_to_assign_to = []
for _ in numbers:
row_nums_to_assign_to.append(row_num)
row_num = (row_num + 1) % 3
# This is the last row position that we are assigning to in the round-robin assignment. Return this for the
# assignment of the numbers from the next column
last_row_num = row_num
row_nums_to_assign_to.sort()
for number, row_num in zip(numbers, row_nums_to_assign_to):
ticket[row_num].append(number)
return last_row_num
def demo_ticket_generation() -> None:
for i in range(6):
sample_ticket = generate_ticket()
print("Structural representation")
print(sample_ticket.structural_display())
demo_ticket_generation()