@happychef/algorithm 1.1.1 → 1.1.2
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 +1 -1
- package/simulateTableAssignment.js +53 -67
package/package.json
CHANGED
|
@@ -135,14 +135,15 @@ function calculateDistance(tableA, tableB) {
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
/**
|
|
138
|
-
* OPTIMIZED: Backtracking
|
|
139
|
-
*
|
|
140
|
-
* -
|
|
141
|
-
* -
|
|
142
|
-
* - Early termination
|
|
138
|
+
* HIGHLY OPTIMIZED: Backtracking with aggressive pruning for multi-table assignment.
|
|
139
|
+
* Key optimizations:
|
|
140
|
+
* - Tables are PRE-FILTERED, so no validity checks needed here
|
|
141
|
+
* - Aggressive branch pruning with capacity tracking
|
|
142
|
+
* - Early termination on optimal solutions
|
|
143
|
+
* - Minimal logging to reduce overhead
|
|
143
144
|
*/
|
|
144
|
-
function findMultiTableCombination(tables, guestsNeeded, startIndex, currentSet, best
|
|
145
|
-
// Base case: All guests
|
|
145
|
+
function findMultiTableCombination(tables, guestsNeeded, startIndex, currentSet, best) {
|
|
146
|
+
// Base case: All guests seated
|
|
146
147
|
if (guestsNeeded <= 0) {
|
|
147
148
|
// Calculate total distance for the current combination
|
|
148
149
|
let distanceSum = 0;
|
|
@@ -151,82 +152,72 @@ function findMultiTableCombination(tables, guestsNeeded, startIndex, currentSet,
|
|
|
151
152
|
distanceSum += calculateDistance(currentSet[i], currentSet[j]);
|
|
152
153
|
}
|
|
153
154
|
}
|
|
154
|
-
// Update
|
|
155
|
+
// Update if better (fewer tables, or same count with lower distance)
|
|
155
156
|
if (currentSet.length < best.tableCount || (currentSet.length === best.tableCount && distanceSum < best.minDistance)) {
|
|
156
157
|
best.minDistance = distanceSum;
|
|
157
|
-
best.tables = [...currentSet];
|
|
158
|
+
best.tables = [...currentSet];
|
|
158
159
|
best.tableCount = currentSet.length;
|
|
159
|
-
console.log(`Found new best table combination: ${best.tables.map(t => t.tableNumber).join(', ')}`);
|
|
160
160
|
}
|
|
161
161
|
return;
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
|
|
164
|
+
// PRUNING: Can't improve on current best table count
|
|
165
|
+
if (currentSet.length >= best.tableCount && best.tableCount !== Infinity) {
|
|
165
166
|
return;
|
|
166
167
|
}
|
|
167
168
|
|
|
168
|
-
//
|
|
169
|
-
if (
|
|
170
|
-
return;
|
|
169
|
+
// PRUNING: Not enough tables remaining
|
|
170
|
+
if (startIndex >= tables.length) {
|
|
171
|
+
return;
|
|
171
172
|
}
|
|
172
173
|
|
|
173
|
-
//
|
|
174
|
-
let
|
|
174
|
+
// PRUNING: Calculate remaining capacity (tables are pre-filtered, so all are valid)
|
|
175
|
+
let maxRemainingCapacity = 0;
|
|
175
176
|
for (let i = startIndex; i < tables.length; i++) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
{
|
|
181
|
-
maxPossibleCapacity += tbl.maxCapacity;
|
|
182
|
-
}
|
|
177
|
+
maxRemainingCapacity += tables[i].maxCapacity;
|
|
178
|
+
}
|
|
179
|
+
if (maxRemainingCapacity < guestsNeeded) {
|
|
180
|
+
return; // Impossible to satisfy
|
|
183
181
|
}
|
|
184
182
|
|
|
185
|
-
|
|
186
|
-
|
|
183
|
+
// OPTIMIZATION: Limit search depth for large table sets
|
|
184
|
+
const remainingTables = tables.length - startIndex;
|
|
185
|
+
if (remainingTables > 15 && currentSet.length === 0) {
|
|
186
|
+
// For first iteration with many tables, only try largest tables
|
|
187
|
+
const sortedBySize = tables.slice(startIndex).sort((a, b) => b.maxCapacity - a.maxCapacity);
|
|
188
|
+
const topTables = sortedBySize.slice(0, Math.min(10, sortedBySize.length));
|
|
189
|
+
|
|
190
|
+
for (const tbl of topTables) {
|
|
191
|
+
const canSeat = Math.min(tbl.maxCapacity, guestsNeeded);
|
|
192
|
+
if (canSeat >= tbl.minCapacity || canSeat >= guestsNeeded) {
|
|
193
|
+
currentSet.push(tbl);
|
|
194
|
+
findMultiTableCombination(tables, guestsNeeded - canSeat, startIndex + 1, currentSet, best);
|
|
195
|
+
currentSet.pop();
|
|
196
|
+
|
|
197
|
+
if (best.tableCount === 1) return; // Found optimal
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return;
|
|
187
201
|
}
|
|
188
202
|
|
|
203
|
+
// Standard backtracking for remaining cases
|
|
189
204
|
for (let i = startIndex; i < tables.length; i++) {
|
|
190
205
|
const tbl = tables[i];
|
|
191
|
-
|
|
192
|
-
// --- Core Checks ---
|
|
193
|
-
// 1. Check if temporary table is valid for this date/time
|
|
194
|
-
if (!isTemporaryTableValid(tbl, reservationDateStr, reservationTimeStr)) {
|
|
195
|
-
continue; // Skip invalid temporary table
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// 2. Check if table is free for all required slots
|
|
199
|
-
if (!isTableFreeForAllSlots(tbl.tableNumber, requiredSlots, tableOccupiedSlots)) {
|
|
200
|
-
continue; // Skip occupied table
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// 3. Check if table contributes meaningfully
|
|
204
206
|
const canSeat = Math.min(tbl.maxCapacity, guestsNeeded);
|
|
207
|
+
|
|
208
|
+
// Skip if table can't contribute meaningfully
|
|
205
209
|
if (canSeat < tbl.minCapacity && canSeat < guestsNeeded) {
|
|
206
|
-
continue;
|
|
210
|
+
continue;
|
|
207
211
|
}
|
|
208
212
|
|
|
209
|
-
if (canSeat <= 0) continue;
|
|
210
|
-
|
|
211
|
-
// --- Recurse ---
|
|
212
|
-
currentSet.push(tbl); // Add table to current combination
|
|
213
|
-
|
|
214
|
-
findMultiTableCombination(
|
|
215
|
-
tables,
|
|
216
|
-
guestsNeeded - canSeat, // Reduce guests needed
|
|
217
|
-
i + 1, // Explore next tables
|
|
218
|
-
currentSet,
|
|
219
|
-
best,
|
|
220
|
-
requiredSlots,
|
|
221
|
-
tableOccupiedSlots,
|
|
222
|
-
reservationDateStr,
|
|
223
|
-
reservationTimeStr
|
|
224
|
-
);
|
|
213
|
+
if (canSeat <= 0) continue;
|
|
225
214
|
|
|
226
|
-
currentSet.
|
|
215
|
+
currentSet.push(tbl);
|
|
216
|
+
findMultiTableCombination(tables, guestsNeeded - canSeat, i + 1, currentSet, best);
|
|
217
|
+
currentSet.pop();
|
|
227
218
|
|
|
228
|
-
//
|
|
229
|
-
if (best.tableCount
|
|
219
|
+
// EARLY EXIT: Found optimal single or two-table solution
|
|
220
|
+
if (best.tableCount <= 2) {
|
|
230
221
|
return;
|
|
231
222
|
}
|
|
232
223
|
}
|
|
@@ -304,11 +295,7 @@ function assignTablesForGivenTime(restaurantData, date, time, guests, tableOccup
|
|
|
304
295
|
guests,
|
|
305
296
|
0, // Start index
|
|
306
297
|
[], // Initial empty set
|
|
307
|
-
best
|
|
308
|
-
requiredSlots,
|
|
309
|
-
tableOccupiedSlots,
|
|
310
|
-
date, // Pass context
|
|
311
|
-
time // Pass context
|
|
298
|
+
best // Best solution object (no need for slots/occupancy - already filtered)
|
|
312
299
|
);
|
|
313
300
|
|
|
314
301
|
if (best.tables.length > 0) {
|
|
@@ -455,10 +442,9 @@ function isTimeAvailableSync(restaurantData, date, time, guests, reservations, s
|
|
|
455
442
|
findMultiTableCombination(
|
|
456
443
|
validTables, // Use pre-filtered tables
|
|
457
444
|
guests,
|
|
458
|
-
0,
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
date, time
|
|
445
|
+
0, // Start index
|
|
446
|
+
[], // Empty current set
|
|
447
|
+
best // Best solution
|
|
462
448
|
);
|
|
463
449
|
|
|
464
450
|
const result = best.tables.length > 0;
|