-
Notifications
You must be signed in to change notification settings - Fork 0
/
DesignAFoodRatingSystem.ts
345 lines (314 loc) · 11.5 KB
/
DesignAFoodRatingSystem.ts
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
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
// Source : https://leetcode.com/problems/design-a-food-rating-system/
// Author : francisco
// Date : 2023-12-19
/*****************************************************************************************************
*
* Design a food rating system that can do the following:
*
* Modify the rating of a food item listed in the system.
* Return the highest-rated food item for a type of cuisine in the system.
*
* Implement the FoodRatings class:
*
* FoodRatings(String[] foods, String[] cuisines, int[] ratings) Initializes the system. The
* food items are described by foods, cuisines and ratings, all of which have a length of n.
*
* foods[i] is the name of the i^th food,
* cuisines[i] is the type of cuisine of the i^th food, and
* ratings[i] is the initial rating of the i^th food.
*
* void changeRating(String food, int newRating) Changes the rating of the food item with the
* name food.
* String highestRated(String cuisine) Returns the name of the food item that has the highest
* rating for the given type of cuisine. If there is a tie, return the item with the lexicographically
* smaller name.
*
* Note that a string x is lexicographically smaller than string y if x comes before y in dictionary
* order, that is, either x is a prefix of y, or if i is the first position such that x[i] != y[i],
* then x[i] comes before y[i] in alphabetic order.
*
* Example 1:
*
* Input
* ["FoodRatings", "highestRated", "highestRated", "changeRating", "highestRated", "changeRating",
* "highestRated"]
* [[["kimchi", "miso", "sushi", "moussaka", "ramen", "bulgogi"], ["korean", "japanese", "japanese",
* "greek", "japanese", "korean"], [9, 12, 8, 15, 14, 7]], ["korean"], ["japanese"], ["sushi", 16],
* ["japanese"], ["ramen", 16], ["japanese"]]
* Output
* [null, "kimchi", "ramen", null, "sushi", null, "ramen"]
*
* Explanation
* FoodRatings foodRatings = new FoodRatings(["kimchi", "miso", "sushi", "moussaka", "ramen",
* "bulgogi"], ["korean", "japanese", "japanese", "greek", "japanese", "korean"], [9, 12, 8, 15, 14,
* 7]);
* foodRatings.highestRated("korean"); // return "kimchi"
* // "kimchi" is the highest rated korean food with a rating of 9.
* foodRatings.highestRated("japanese"); // return "ramen"
* // "ramen" is the highest rated japanese food with a rating
* of 14.
* foodRatings.changeRating("sushi", 16); // "sushi" now has a rating of 16.
* foodRatings.highestRated("japanese"); // return "sushi"
* // "sushi" is the highest rated japanese food with a rating
* of 16.
* foodRatings.changeRating("ramen", 16); // "ramen" now has a rating of 16.
* foodRatings.highestRated("japanese"); // return "ramen"
* // Both "sushi" and "ramen" have a rating of 16.
* // However, "ramen" is lexicographically smaller than "sushi".
*
* Constraints:
*
* 1 <= n <= 2 * 10^4
* n == foods.length == cuisines.length == ratings.length
* 1 <= foods[i].length, cuisines[i].length <= 10
* foods[i], cuisines[i] consist of lowercase English letters.
* 1 <= ratings[i] <= 10^8
* All the strings in foods are distinct.
* food will be the name of a food item in the system across all calls to changeRating.
* cuisine will be a type of cuisine of at least one food item in the system across all calls
* to highestRated.
* At most 2 * 10^4 calls in total will be made to changeRating and highestRated.
******************************************************************************************************/
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/**
* Runtime: 1176ms (100.00%)
* Memory: 109.27MB (100.00%)
*/
export class FoodRatings {
foods: Record<string, string>;
cuisines: Record<string, Array<{ food: string; rating: number }>>;
/**
* @param {string[]} foods
* @param {string[]} cuisines
* @param {number[]} ratings
* ASSUME: foods.length === cuisines.length === ratings.length
*/
constructor(foods: string[], cuisines: string[], ratings: number[]) {
this.foods = {};
for (let index: number = 0; index < foods.length; index++) {
this.foods[foods[index] as string] = cuisines[index] as string;
}
this.cuisines = {};
for (let index: number = 0; index < cuisines.length; index++) {
if (this.cuisines[cuisines[index] as string] !== undefined) {
this.insert(
this.cuisines[cuisines[index] as string]!,
foods[index] as string,
ratings[index] as number,
);
} else {
this.cuisines[cuisines[index] as string] = [
{ food: foods[index] as string, rating: ratings[index] as number },
];
}
}
}
/**
* @private
* @param {Array<{ food: string, rating: number }>} heap
* @param {string} food
* @param {number} rating
* @returns {void}
* insert a given food and its rating into the heap
* Stub:
insert(heap: Array<{ food: string, rating: number }>, food: string, rating: number): void {(...)}
*/
private insert(
heap: Array<{ food: string; rating: number }>,
food: string,
rating: number,
): void {
heap.push({ food, rating });
this.heapifyUp(heap);
}
/**
* @private
* @param {Array<{ food: string, rating: number }>} heap
* @param {number} startIndex
* @returns {void}
* <...>
* Stub:
heapifyUp(heap: Array<{ food: string, rating: number }>, startIndex: number = heap.length - 1): void {(...)}
*/
private heapifyUp(
heap: Array<{ food: string; rating: number }>,
startIndex: number = heap.length - 1,
): void {
let currentIndex: number = startIndex;
while (currentIndex > 0) {
const parentIndex = Math.floor((currentIndex - 1) / 2);
if (
heap[currentIndex]!.rating > heap[parentIndex]!.rating ||
(heap[currentIndex]!.rating === heap[parentIndex]!.rating &&
this.lexicographicallySmaller(
heap[currentIndex]!.food,
heap[parentIndex]!.food,
))
) {
this.swap(heap, currentIndex, parentIndex);
currentIndex = parentIndex;
} else break;
}
}
/**
* @private
* @param {Array<{ food: string, rating: number }>} heap
* @param {number} startIndex
* @returns {void}
* <...>
* Stub:
heapifyDown(heap: Array<{ food: string, rating: number }>, startIndex: number = 0): void {(...)}
*/
private heapifyDown(
heap: Array<{ food: string; rating: number }>,
startIndex: number = 0,
): void {
let currentIndex: number = startIndex;
while (true) {
const leftChildIndex: number = 2 * currentIndex + 1;
const rightChildIndex: number = 2 * currentIndex + 2;
let maxIndex: number = currentIndex;
if (
leftChildIndex < heap.length &&
(heap[leftChildIndex]!.rating > heap[maxIndex]!.rating ||
(heap[leftChildIndex]!.rating === heap[maxIndex]!.rating &&
this.lexicographicallySmaller(
heap[leftChildIndex]!.food,
heap[maxIndex]!.food,
)))
) {
maxIndex = leftChildIndex;
}
if (
rightChildIndex < heap.length &&
(heap[rightChildIndex]!.rating > heap[maxIndex]!.rating ||
(heap[rightChildIndex]!.rating === heap[maxIndex]!.rating &&
this.lexicographicallySmaller(
heap[rightChildIndex]!.food,
heap[maxIndex]!.food,
)))
) {
maxIndex = rightChildIndex;
}
if (currentIndex !== maxIndex) {
this.swap(heap, currentIndex, maxIndex);
currentIndex = maxIndex;
} else break;
}
}
/**
* @private
* @param {Array<{ food: string, rating: number }>} heap
* @param {string} food
* @param {number} newRating
* @returns {void}
* <...>
* Stub:
changePriority(heap: Array<{ food: string, rating: number }>, food: string, newRating: number): void {(...)}
*/
private changePriority(
heap: Array<{ food: string; rating: number }>,
food: string,
newRating: number,
): void {
const index: number = this.findIndex(heap, food);
if (index !== -1) {
heap[index]!.rating = newRating;
this.heapifyUp(heap, index);
this.heapifyDown(heap, index);
}
}
/**
* @private
* @param {Array<{ food: string, rating: number }>} heap
* @param {string} food
* @returns {number}
* find index of the given string, food, in the given heap, heap
* Stub:
findIndex(heap: Array<{ food: string, rating: number }>, food: string): number {(...) return 0}
*/
private findIndex(
heap: Array<{ food: string; rating: number }>,
food: string,
): number {
for (let index: number = 0; index < heap.length; index++) {
if (heap[index]!.food === food) return index;
}
return -1;
}
/**
* @private
* @param {Array<{ food: string, rating: number }>} heap
* @param {number} index1
* @param {number} index2
* @returns {void}
* <...>
* Stub:
swap(heap: Array<{ food: string, rating: number }>, index1: number, index2: number): void {(...)}
*/
private swap(
heap: Array<{ food: string; rating: number }>,
index1: number,
index2: number,
): void {
const temp: { food: string; rating: number } = heap[index1]!;
heap[index1] = heap[index2] as { food: string; rating: number };
heap[index2] = temp;
}
/**
* @param {string} food
* @param {number} newRating
* @returns {void}
* modify the rating of a food item listed in the system, with the given name food
* Stub:
changeRating(food: string, newRating: number): void {(...)}
*/
changeRating(food: string, newRating: number): void {
this.changePriority(
this.cuisines[this.foods[food] as string]!,
food,
newRating,
);
}
/**
* @param {string} cuisine
* return the name of the food item that has the highest rating fot the given type of cuisine
* NOTE: if there is a tie return the item with the lexicographically smaller name
* Stub:
highestRated(cuisine: string): string {(...) return ""}
* Tests:
* for new FoodRatings(["kimchi","miso","sushi","moussaka","ramen","bulgogi"],["korean","japanese","japanese","greek","japanese","korean"],[9,12,8,15,14,7]):
* I: cuisine = "greek" -> O: "moussaka"
* I: cuisine = "japanese" -> O: "ramen"
* I: cuisine = "korean" -> O: "kimchi"
*/
highestRated(cuisine: string): string {
return this.cuisines[cuisine]![0]!.food;
}
/**
* @private
* @param {string} a
* @param {string} b
* @returns {boolean}
* given two strings, a & b, return true if a is lexicographically smaller than b
* A string x is lexicographically smaller than string y if either x is a prefix of y
* or if i is the first position such that x[i] !== y[i], then x[i] comes before y[i]
* Stub:
function lexicographicallySmaller(a: string, b: string): string {return ""}
*/
private lexicographicallySmaller(a: string, b: string): boolean {
const length: number = Math.min(a.length, b.length);
for (let i: number = 0; i < length; i++) {
if (a[i] !== b[i]) {
return a[i]!.charCodeAt(0) < b[i]!.charCodeAt(0);
}
}
return a.length <= b.length;
}
}
/**
* Your FoodRatings object will be instantiated and called as such:
* var obj = new FoodRatings(foods, cuisines, ratings)
* obj.changeRating(food,newRating)
* var param_2 = obj.highestRated(cuisine)
*/