-
Notifications
You must be signed in to change notification settings - Fork 0
/
Optimal.py
227 lines (191 loc) · 8.49 KB
/
Optimal.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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
import os
import json
from copy import deepcopy
from time import time
# from Dataset import Dataset
class Optimal:
def __init__(self, dataset, threshold):
self.dataset = dataset
self.threshold = threshold
def update_learner_profile(self, learner, course):
"""Update the learner profile with the skills and levels provided by the course
Args:
learner (list): list of skills and mastery level of the learner
course (list): list of required (resp. provided) skills and mastery level of the course
"""
# Update the learner profile with the skills and levels provided by the course (course [1] is the list of skills and levels provided by the course)
for cskill, clevel in course[1]:
found = False
i = 0
while not found and i < len(learner):
lskill, llevel = learner[i]
if cskill == lskill:
learner[i] = (lskill, max(llevel, clevel))
found = True
i += 1
if not found:
learner.append((cskill, clevel))
def update_learner_profile_list(self, learner, course_list):
"""Update the learner profile with the skills and levels provided by all courses in the course list
Args:
learner (list): list of skills and mastery level of the learner
course_list (list): list of courses
"""
for id_c in course_list:
self.update_learner_profile(learner, self.dataset.courses[id_c])
def get_course_recommendation(
self,
learner,
enrollable_courses,
candiate_course_recommendation_list,
course_recommendations_list,
max_nb_applicable_jobs,
max_attractiveness,
k,
):
"""Recursively get the optimal sequence of courses for a learner
Args:
learner (list): list of skills and mastery level of the learner
enrollable_courses (dict): dictionary of courses that the learner can enroll in
candiate_course_recommendation_list (list): list of candiate courses for the recommendation
course_recommendations_list (list): optimal sequence of courses for the recommendation to be returned
max_nb_applicable_jobs (int): current maximum number of applicable jobs given the recommendation list
max_attractiveness (int): current maximum attractiveness given the recommendation list
k (int): number of courses to recommend
Returns:
tuple: optimal sequence of courses for the recommendation, current maximum number of applicable jobs, current maximum attractiveness
"""
if k == 0:
# Base case: return the current recommendation list if the number of applicable jobs is greater than the current maximum
nb_applicable_jobs = self.dataset.get_nb_applicable_jobs(
learner, self.threshold
)
attractiveness = self.dataset.get_learner_attractiveness(learner)
if nb_applicable_jobs > max_nb_applicable_jobs:
course_recommendations_list = candiate_course_recommendation_list
max_nb_applicable_jobs = nb_applicable_jobs
max_attractiveness = attractiveness
# If there are multiple courses that maximize the number of applicable jobs,
# select the one that maximizes the attractiveness of the learner
elif (
nb_applicable_jobs == max_nb_applicable_jobs
and attractiveness > max_attractiveness
):
course_recommendations_list = candiate_course_recommendation_list
max_nb_applicable_jobs = nb_applicable_jobs
max_attractiveness = attractiveness
return (
course_recommendations_list,
max_nb_applicable_jobs,
max_attractiveness,
)
# Recursive case: for all courses that the learner can enroll in, get the optimal sequence of courses by calling the function recursively
else:
enrollable_courses = self.dataset.get_all_enrollable_courses(
learner, self.threshold
)
for id_c, course in enrollable_courses.items():
tmp_learner = deepcopy(learner)
self.update_learner_profile(tmp_learner, course)
new_candidate_list = candiate_course_recommendation_list + [id_c]
(
course_recommendations_list,
max_nb_applicable_jobs,
max_attractiveness,
) = self.get_course_recommendation(
tmp_learner,
enrollable_courses,
new_candidate_list,
course_recommendations_list,
max_nb_applicable_jobs,
max_attractiveness,
k - 1,
)
return (
course_recommendations_list,
max_nb_applicable_jobs,
max_attractiveness,
)
def recommend_and_update(self, learner, k):
"""Recommend a sequence of courses to the learner and update the learner profile
Args:
learner (list): list of skills and mastery level of the learner
k (int): number of courses to recommend
Returns:
list: optimal sequence of courses for the recommendation
"""
enrollable_courses = None
candiate_course_recommendation_list = []
course_recommendations_list = None
max_nb_applicable_jobs = 0
max_attractiveness = 0
(
course_recommendations_list,
max_nb_applicable_jobs,
max_attractiveness,
) = self.get_course_recommendation(
learner,
enrollable_courses,
candiate_course_recommendation_list,
course_recommendations_list,
max_nb_applicable_jobs,
max_attractiveness,
k,
)
self.update_learner_profile_list(learner, course_recommendations_list)
return course_recommendations_list
def optimal_recommendation(self, k, run):
"""Recommend a sequence of courses to all learners and save the results
Args:
k (int): number of courses to recommend
run (int): run number
"""
results = dict()
avg_l_attrac = self.dataset.get_avg_learner_attractiveness()
print(f"The average attractiveness of the learners is {avg_l_attrac:.2f}")
results["original_attractiveness"] = avg_l_attrac
avg_app_j = self.dataset.get_avg_applicable_jobs(self.threshold)
print(f"The average nb of applicable jobs per learner is {avg_app_j:.2f}")
results["original_applicable_jobs"] = avg_app_j
time_start = time()
recommendations = dict()
for i, learner in enumerate(self.dataset.learners):
index = self.dataset.learners_index[i]
recommendation_sequence = self.recommend_and_update(
self.dataset.learners[i], k
)
recommendations[index] = [
self.dataset.courses_index[course_id]
for course_id in recommendation_sequence
]
time_end = time()
avg_recommendation_time = (time_end - time_start) / len(self.dataset.learners)
print(f"Average Recommendation Time: {avg_recommendation_time:.2f} seconds")
results["avg_recommendation_time"] = avg_recommendation_time
avg_l_attrac = self.dataset.get_avg_learner_attractiveness()
print(f"The new average attractiveness of the learners is {avg_l_attrac:.2f}")
results["new_attractiveness"] = avg_l_attrac
avg_app_j = self.dataset.get_avg_applicable_jobs(self.threshold)
print(f"The new average nb of applicable jobs per learner is {avg_app_j:.2f}")
results["new_applicable_jobs"] = avg_app_j
results["recommendations"] = recommendations
filename = (
"optimal_nbskills_"
+ str(len(self.dataset.skills))
+ "_k_"
+ str(k)
+ "_run_"
+ str(run)
+ ".json"
)
json.dump(
results,
open(
os.path.join(
self.dataset.config["results_path"],
filename,
),
"w",
),
indent=4,
)