@happychef/algorithm 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happychef/algorithm",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Restaurant and reservation algorithm utilities",
5
5
  "main": "index.js",
6
6
  "author": "happy chef",
@@ -135,7 +135,11 @@ function calculateDistance(tableA, tableB) {
135
135
  }
136
136
 
137
137
  /**
138
- * Backtracking function to find a combination of tables (multi-table assignment).
138
+ * OPTIMIZED: Backtracking function to find a combination of tables (multi-table assignment).
139
+ * Improvements:
140
+ * - Pre-computed valid tables list to avoid redundant checks
141
+ * - Better pruning with capacity tracking
142
+ * - Early termination when exact match found
139
143
  */
140
144
  function findMultiTableCombination(tables, guestsNeeded, startIndex, currentSet, best, requiredSlots, tableOccupiedSlots, reservationDateStr, reservationTimeStr) {
141
145
  // Base case: All guests are seated
@@ -152,7 +156,7 @@ function findMultiTableCombination(tables, guestsNeeded, startIndex, currentSet,
152
156
  best.minDistance = distanceSum;
153
157
  best.tables = [...currentSet]; // Store a copy
154
158
  best.tableCount = currentSet.length;
155
- console.log(`Found new best table combination: ${best.tables.map(t => t.tableNumber).join(', ')} for ${guestsNeeded} guests`);
159
+ console.log(`Found new best table combination: ${best.tables.map(t => t.tableNumber).join(', ')}`);
156
160
  }
157
161
  return;
158
162
  }
@@ -161,21 +165,23 @@ function findMultiTableCombination(tables, guestsNeeded, startIndex, currentSet,
161
165
  return;
162
166
  }
163
167
 
168
+ // OPTIMIZATION: Prune if we already have enough tables in current set
164
169
  if (currentSet.length >= best.tableCount && best.tableCount !== Infinity) {
165
- return; // Pruning based on table count
170
+ return; // Can't improve on current best
166
171
  }
167
172
 
173
+ // OPTIMIZATION: Calculate max possible capacity from remaining tables
168
174
  let maxPossibleCapacity = 0;
169
175
  for (let i = startIndex; i < tables.length; i++) {
170
176
  const tbl = tables[i];
171
177
  // Only consider valid & free tables for potential capacity
172
178
  if (isTemporaryTableValid(tbl, reservationDateStr, reservationTimeStr) &&
173
- isTableFreeForAllSlots(tbl.tableNumber, requiredSlots, tableOccupiedSlots))
179
+ isTableFreeForAllSlots(tbl.tableNumber, requiredSlots, tableOccupiedSlots))
174
180
  {
175
181
  maxPossibleCapacity += tbl.maxCapacity;
176
182
  }
177
183
  }
178
-
184
+
179
185
  if (maxPossibleCapacity < guestsNeeded) {
180
186
  return; // Impossible to seat remaining guests
181
187
  }
@@ -188,24 +194,23 @@ function findMultiTableCombination(tables, guestsNeeded, startIndex, currentSet,
188
194
  if (!isTemporaryTableValid(tbl, reservationDateStr, reservationTimeStr)) {
189
195
  continue; // Skip invalid temporary table
190
196
  }
191
-
197
+
192
198
  // 2. Check if table is free for all required slots
193
199
  if (!isTableFreeForAllSlots(tbl.tableNumber, requiredSlots, tableOccupiedSlots)) {
194
200
  continue; // Skip occupied table
195
201
  }
196
-
202
+
197
203
  // 3. Check if table contributes meaningfully
198
204
  const canSeat = Math.min(tbl.maxCapacity, guestsNeeded);
199
205
  if (canSeat < tbl.minCapacity && canSeat < guestsNeeded) {
200
206
  continue; // Don't use a table for fewer than its min capacity unless it fulfills the remainder
201
207
  }
202
-
208
+
203
209
  if (canSeat <= 0) continue; // Table doesn't help
204
210
 
205
211
  // --- Recurse ---
206
212
  currentSet.push(tbl); // Add table to current combination
207
- console.log(`Trying table ${tbl.tableNumber} (${tbl.minCapacity}-${tbl.maxCapacity}) for ${canSeat}/${guestsNeeded} guests`);
208
-
213
+
209
214
  findMultiTableCombination(
210
215
  tables,
211
216
  guestsNeeded - canSeat, // Reduce guests needed
@@ -217,35 +222,26 @@ function findMultiTableCombination(tables, guestsNeeded, startIndex, currentSet,
217
222
  reservationDateStr,
218
223
  reservationTimeStr
219
224
  );
220
-
225
+
221
226
  currentSet.pop(); // Backtrack: remove table to explore other combinations
227
+
228
+ // OPTIMIZATION: Early exit if we found a single-table solution (best possible)
229
+ if (best.tableCount === 1) {
230
+ return;
231
+ }
222
232
  }
223
233
  }
224
234
 
225
235
  function assignTablesForGivenTime(restaurantData, date, time, guests, tableOccupiedSlots, selectedZitplaats = null) {
226
236
  console.log(`[assignTablesForGivenTime] Processing ${date} ${time} for ${guests} guests`);
227
-
237
+
228
238
  // FIXED: More robust parsing of settings with detailed logging
229
239
  const generalSettings = restaurantData["general-settings"] || {};
230
- console.log("[assignTablesForGivenTime] General settings:", generalSettings);
231
-
232
- // Check what format the data is in
233
- console.log("[assignTablesForGivenTime] duurReservatie format:", typeof generalSettings.duurReservatie);
234
- console.log("[assignTablesForGivenTime] intervalReservatie format:", typeof generalSettings.intervalReservatie);
235
-
236
- // Check for MongoDB format
237
- if (typeof generalSettings.duurReservatie === 'object' && generalSettings.duurReservatie?.$numberInt) {
238
- console.log("[assignTablesForGivenTime] duurReservatie is in MongoDB format:", generalSettings.duurReservatie?.$numberInt);
239
- }
240
-
241
- if (typeof generalSettings.intervalReservatie === 'object' && generalSettings.intervalReservatie?.$numberInt) {
242
- console.log("[assignTablesForGivenTime] intervalReservatie is in MongoDB format:", generalSettings.intervalReservatie?.$numberInt);
243
- }
244
-
240
+
245
241
  // DEFAULT VALUES - this approach mirrors the TypeScript implementation
246
242
  let duurReservatie = 120; // Default: 2 hours in minutes
247
243
  let intervalReservatie = 15; // Default: 15 minute intervals
248
-
244
+
249
245
  // Use safeParseInt for robust parsing
250
246
  duurReservatie = safeParseInt(generalSettings.duurReservatie, 120);
251
247
  intervalReservatie = safeParseInt(generalSettings.intervalReservatie, 15);
@@ -256,7 +252,7 @@ function assignTablesForGivenTime(restaurantData, date, time, guests, tableOccup
256
252
  console.error("Invalid interval settings.");
257
253
  return [];
258
254
  }
259
-
255
+
260
256
  const requiredSlots = computeRequiredSlots(time, duurReservatie, intervalReservatie);
261
257
  if (!requiredSlots || requiredSlots.length === 0) {
262
258
  console.error(`Could not compute required slots for ${time}`);
@@ -267,17 +263,33 @@ function assignTablesForGivenTime(restaurantData, date, time, guests, tableOccup
267
263
  const allTables = getAllTables(restaurantData, selectedZitplaats);
268
264
  console.log(`[assignTablesForGivenTime] Checking ${allTables.length} tables`);
269
265
 
266
+ // OPTIMIZATION: Pre-filter and sort tables for better performance
267
+ // Filter out invalid and occupied tables first
268
+ const validTables = allTables.filter(t =>
269
+ isTemporaryTableValid(t, date, time) &&
270
+ isTableFreeForAllSlots(t.tableNumber, requiredSlots, tableOccupiedSlots)
271
+ );
272
+
273
+ if (validTables.length === 0) {
274
+ console.log(`[assignTablesForGivenTime] No valid tables available`);
275
+ return [];
276
+ }
277
+
278
+ // OPTIMIZATION: Sort tables by capacity (prefer exact matches first)
279
+ // This helps find optimal solutions faster
280
+ validTables.sort((a, b) => {
281
+ // Prioritize tables that can seat exactly the number of guests
282
+ const aExact = (a.minCapacity <= guests && guests <= a.maxCapacity) ? 0 : 1;
283
+ const bExact = (b.minCapacity <= guests && guests <= b.maxCapacity) ? 0 : 1;
284
+ if (aExact !== bExact) return aExact - bExact;
285
+
286
+ // Then sort by capacity (smaller tables first to minimize waste)
287
+ return a.maxCapacity - b.maxCapacity;
288
+ });
289
+
270
290
  // --- Try single-table assignment first ---
271
- for (const t of allTables) {
272
- // Use imported validation function
273
- if (!isTemporaryTableValid(t, date, time)) {
274
- continue;
275
- }
276
-
277
- if (t.minCapacity <= guests &&
278
- guests <= t.maxCapacity &&
279
- isTableFreeForAllSlots(t.tableNumber, requiredSlots, tableOccupiedSlots))
280
- {
291
+ for (const t of validTables) {
292
+ if (t.minCapacity <= guests && guests <= t.maxCapacity) {
281
293
  console.log(`[assignTablesForGivenTime] Assigned single table ${t.tableNumber} for ${guests} guests`);
282
294
  return [t.tableNumber];
283
295
  }
@@ -288,7 +300,7 @@ function assignTablesForGivenTime(restaurantData, date, time, guests, tableOccup
288
300
  // --- Try multi-table assignment ---
289
301
  const best = { minDistance: Infinity, tables: [], tableCount: Infinity };
290
302
  findMultiTableCombination(
291
- allTables,
303
+ validTables, // Use pre-filtered and sorted tables
292
304
  guests,
293
305
  0, // Start index
294
306
  [], // Initial empty set
@@ -409,14 +421,28 @@ function isTimeAvailableSync(restaurantData, date, time, guests, reservations, s
409
421
  const allTables = getAllTables(restaurantData, selectedZitplaats); // Get all tables
410
422
  console.log(`[isTimeAvailableSync] Checking ${allTables.length} tables for new reservation`);
411
423
 
412
- // 1. Try single table assignment
413
- for (const t of allTables) {
414
- if (!isTemporaryTableValid(t, date, time)) continue; // Check temporary validity
424
+ // OPTIMIZATION: Pre-filter valid tables
425
+ const validTables = allTables.filter(t =>
426
+ isTemporaryTableValid(t, date, time) &&
427
+ isTableFreeForAllSlots(t.tableNumber, requiredSlots, tableOccupiedSlots)
428
+ );
429
+
430
+ if (validTables.length === 0) {
431
+ console.log(`[isTimeAvailableSync] No valid tables available`);
432
+ return false;
433
+ }
434
+
435
+ // OPTIMIZATION: Sort for faster exact matches
436
+ validTables.sort((a, b) => {
437
+ const aExact = (a.minCapacity <= guests && guests <= a.maxCapacity) ? 0 : 1;
438
+ const bExact = (b.minCapacity <= guests && guests <= b.maxCapacity) ? 0 : 1;
439
+ if (aExact !== bExact) return aExact - bExact;
440
+ return a.maxCapacity - b.maxCapacity;
441
+ });
415
442
 
416
- if (t.minCapacity <= guests &&
417
- guests <= t.maxCapacity &&
418
- isTableFreeForAllSlots(t.tableNumber, requiredSlots, tableOccupiedSlots))
419
- {
443
+ // 1. Try single table assignment
444
+ for (const t of validTables) {
445
+ if (t.minCapacity <= guests && guests <= t.maxCapacity) {
420
446
  console.log(`[isTimeAvailableSync] Found single table ${t.tableNumber} for ${guests} guests at ${time}`);
421
447
  return true; // Available
422
448
  }
@@ -427,7 +453,7 @@ function isTimeAvailableSync(restaurantData, date, time, guests, reservations, s
427
453
  // 2. Try multi-table assignment
428
454
  const best = { minDistance: Infinity, tables: [], tableCount: Infinity };
429
455
  findMultiTableCombination(
430
- allTables,
456
+ validTables, // Use pre-filtered tables
431
457
  guests,
432
458
  0, [], best,
433
459
  requiredSlots,
@@ -441,7 +467,7 @@ function isTimeAvailableSync(restaurantData, date, time, guests, reservations, s
441
467
  } else {
442
468
  console.log(`[isTimeAvailableSync] No table combination available`);
443
469
  }
444
-
470
+
445
471
  return result; // Available if a combination was found
446
472
 
447
473
  } catch (error) {