-
Notifications
You must be signed in to change notification settings - Fork 1
/
parser.py
151 lines (127 loc) · 5.99 KB
/
parser.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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
"""
Hashcode 2020 data structures and parsing helpers
"""
import logging
from typing import List, Set, TextIO, Dict
class Library: # pylint: disable=too-few-public-methods
"""Library"""
def __init__(self, id_: int, # pylint: disable=too-many-arguments
n_books: int, signup_delay: int, books_per_day: int, books: Set[int]):
self.id_ = id_
self.n_books = n_books
self.signup_delay = signup_delay # days
self.books_per_day = books_per_day # books/day
self.books = books # books id set
class InputDataSet: # pylint: disable=too-few-public-methods
"""Input Data"""
def __init__(self, n_books, # pylint: disable=too-many-arguments
n_libraries, n_days, book_scores, libraries):
self.n_books = n_books
self.n_libraries = n_libraries
self.n_days = n_days
self.book_scores: List[int] = book_scores # book i score = book_scores[i]
self.libraries: List[Library] = libraries # List[Library]
def __str__(self):
return f"B: {self.n_books} L: {self.n_libraries} D: {self.n_days}"
class LibraryOrder: # pylint: disable=too-few-public-methods
"""Library Order"""
def __init__(self, id_: int, n_books: int, books: List[int]):
self.id_ = id_
self.n_books = n_books
self.books = books # book id list (ordered)
class OutputDataSet: # pylint: disable=too-few-public-methods
"""Output Data"""
def __init__(self, n_libraries, library_orders: List[LibraryOrder]):
self.n_libraries = n_libraries
self.library_orders = library_orders # List[LibraryOrder]
def __str__(self):
return f"L: {self.n_libraries}"
def parse_input_file(input_file) -> InputDataSet:
"""
Parse input file and return an initialized InputDataSet struct
:param input_file: file for parsing
:return: InputDataSet
"""
# B, L, D
(total_books, total_libraries, total_days) = map(int, input_file.readline().split(' '))
book_scores = list(map(int, input_file.readline().split(' ')))
libraries = []
for id_ in range(total_libraries):
(n_books, signup_delay, books_per_day) = map(int, input_file.readline().split(' '))
books = set(map(int, input_file.readline().split(' ')))
libraries.append(Library(id_, n_books, signup_delay, books_per_day, books))
return InputDataSet(total_books, total_libraries, total_days, book_scores, libraries)
def parse_output_file(output_file, n_libraries, n_books) -> (bool, OutputDataSet):
"""
Parse output file and return an initialized OutputDataSet struct
:param output_file: file for parsing
:param n_libraries: number of libraries from input file
:param n_books: number of books from input file
:return: OutputDataSet
"""
n_orders = int(output_file.readline()) # L
library_orders = []
library_found: Dict[int, int] = {} # library, line 1st definition
books_found: Dict[int, int] = {} # book, line 1st definition
for line in range(n_orders):
current_line = 2 * line + 2
library_definition = output_file.readline()
try:
(id_, lib_n_books) = map(int, library_definition.split(' '))
if id_ < 0 or id_ >= n_libraries:
logging.warning(f"line {current_line}: library id {id_} is invalid should be >= 0 and < {n_libraries}")
continue
if lib_n_books == 0:
logging.warning(f"line {current_line}: library books sent {lib_n_books} == 0")
if id_ in library_found:
logging.warning(f"line {current_line}: library {id_} previously defined line {library_found[id_]}")
continue
else:
library_found[id_] = current_line
except ValueError as _:
logging.warning(
f"line {current_line}: invalid content: '{library_definition}' should be <library_id> <books>")
continue
current_line = 2 * line + 3
library_books = output_file.readline()
try:
books = list(map(int, library_books.split(' ')))
for book in books:
if book < 0 or book >= n_books:
logging.warning(f"line {current_line}: book id {book} is invalid should be >= 0 and < {n_books}")
if book in books_found:
logging.debug(f"line {current_line}: book {book} previously defined line {books_found[book]}")
else:
books_found[book] = current_line
except ValueError as _:
logging.warning(f"line {current_line}: invalid content: '{library_books}' should be <books>...")
continue
if len(books) != lib_n_books:
logging.warning(f"line {current_line}: number of books ({len(books)}) does not match declaration at line {current_line - 1} ({lib_n_books})")
library_orders.append(LibraryOrder(id_, lib_n_books, books))
return OutputDataSet(n_orders, library_orders)
def write_output_file(output_data: OutputDataSet, output_file: TextIO):
"""
Dump output data (solution) to output file
:param output_data: solution
:param output_file: file for dumping
"""
output_file.write(f"{output_data.n_libraries}\n")
for library_order in output_data.library_orders:
output_file.write(f"{library_order.id_} {library_order.n_books}\n")
output_file.write(" ".join(str(lib) for lib in library_order.books) + "\n")
def build_signup_schedule(input_data_set: InputDataSet, output_data_set: OutputDataSet):
"""
Return signup schedule i.e (day signup complete, library id) list
Note: returned list in arbitrary order
:param input_data_set:
:param output_data_set:
:return: (day signup complete, library id) list
"""
day = 0
signup_schedule = []
for library_order in output_data_set.library_orders:
library = input_data_set.libraries[library_order.id_]
day += library.signup_delay
signup_schedule.append((day, library.id_))
return signup_schedule