Weighted Interval Scheduling Calculator

Weighted Interval Scheduling Calculator & Guide :root { –primary-color: #004a99; –success-color: #28a745; –background-color: #f8f9fa; –text-color: #333; –border-color: #ddd; –shadow-color: rgba(0, 0, 0, 0.1); } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: var(–background-color); color: var(–text-color); line-height: 1.6; margin: 0; padding: 0; } .container { max-width: 1000px; margin: 20px auto; padding: 20px; background-color: #fff; box-shadow: 0 2px 10px var(–shadow-color); border-radius: 8px; } header { text-align: center; padding-bottom: 20px; border-bottom: 1px solid var(–border-color); margin-bottom: 20px; } h1 { color: var(–primary-color); margin-bottom: 10px; } h2, h3 { color: var(–primary-color); margin-top: 25px; margin-bottom: 15px; } .calculator-section { margin-bottom: 30px; padding: 25px; background-color: #fdfdfd; border: 1px solid var(–border-color); border-radius: 6px; } .calculator-section h2 { margin-top: 0; text-align: center; color: var(–primary-color); } .input-group { margin-bottom: 20px; display: flex; flex-direction: column; align-items: flex-start; } .input-group label { display: block; margin-bottom: 8px; font-weight: bold; color: var(–primary-color); } .input-group input[type="number"], .input-group input[type="text"], .input-group select { width: calc(100% – 20px); padding: 10px; border: 1px solid var(–border-color); border-radius: 4px; font-size: 1rem; box-sizing: border-box; } .input-group input[type="number"]:focus, .input-group input[type="text"]:focus, .input-group select:focus { outline: none; border-color: var(–primary-color); box-shadow: 0 0 0 2px rgba(0, 74, 153, 0.2); } .input-group .helper-text { font-size: 0.85em; color: #666; margin-top: 5px; } .error-message { color: #dc3545; font-size: 0.85em; margin-top: 5px; display: none; /* Hidden by default */ } .button-group { display: flex; justify-content: space-between; margin-top: 20px; flex-wrap: wrap; gap: 10px; } .button-group button { padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; font-size: 1rem; font-weight: bold; transition: background-color 0.3s ease; flex: 1; min-width: 150px; } .calculate-button { background-color: var(–primary-color); color: white; } .calculate-button:hover { background-color: #003366; } .reset-button { background-color: #6c757d; color: white; } .reset-button:hover { background-color: #5a6268; } .copy-button { background-color: var(–success-color); color: white; } .copy-button:hover { background-color: #218838; } .results-container { margin-top: 30px; padding: 25px; background-color: var(–primary-color); color: white; border-radius: 6px; text-align: center; } .results-container h2 { color: white; margin-top: 0; } .main-result { font-size: 2.5em; font-weight: bold; margin: 15px 0; padding: 10px; background-color: rgba(255, 255, 255, 0.2); border-radius: 4px; } .intermediate-results div { margin-bottom: 10px; font-size: 1.1em; } .formula-explanation { font-size: 0.9em; margin-top: 15px; opacity: 0.8; } table { width: 100%; border-collapse: collapse; margin-top: 20px; margin-bottom: 20px; box-shadow: 0 1px 5px var(–shadow-color); } th, td { padding: 12px 15px; text-align: left; border: 1px solid var(–border-color); } thead { background-color: var(–primary-color); color: white; } tbody tr:nth-child(even) { background-color: #f2f2f2; } caption { font-size: 1.1em; font-weight: bold; color: var(–primary-color); margin-bottom: 10px; text-align: left; } canvas { display: block; margin: 20px auto; max-width: 100%; border: 1px solid var(–border-color); border-radius: 4px; } .chart-caption { text-align: center; font-size: 0.9em; color: #666; margin-top: 5px; } .article-content { margin-top: 40px; padding-top: 30px; border-top: 1px solid var(–border-color); } .article-content h2, .article-content h3 { color: var(–primary-color); } .article-content p, .article-content ul, .article-content ol { margin-bottom: 15px; } .article-content ul, .article-content ol { padding-left: 25px; } .article-content li { margin-bottom: 8px; } .faq-item { margin-bottom: 15px; padding: 10px; border-left: 3px solid var(–primary-color); background-color: #f9f9f9; border-radius: 4px; } .faq-item strong { color: var(–primary-color); } .internal-links { margin-top: 30px; padding: 20px; background-color: #eef7ff; border-radius: 6px; border: 1px solid #cce5ff; } .internal-links h3 { margin-top: 0; color: var(–primary-color); } .internal-links ul { list-style: none; padding: 0; } .internal-links li { margin-bottom: 10px; } .internal-links a { color: var(–primary-color); text-decoration: none; font-weight: bold; } .internal-links a:hover { text-decoration: underline; } .internal-links span { font-size: 0.9em; color: #555; display: block; margin-top: 3px; } @media (max-width: 768px) { .container { margin: 10px; padding: 15px; } .button-group { flex-direction: column; align-items: stretch; } .button-group button { width: 100%; margin-bottom: 10px; } }

Weighted Interval Scheduling Calculator

Optimize your task selection for maximum total weight.

Weighted Interval Scheduling Calculator

Enter the total number of intervals (tasks) available.

Calculation Results

The maximum total weight is found using dynamic programming. We sort intervals by finish time and iteratively compute the maximum weight achievable up to each interval.

Schedule Visualization

Visual representation of selected intervals and their weights.
Interval Details and Selection Status
Interval ID Start Time Finish Time Weight Selected

What is Weighted Interval Scheduling?

The weighted interval scheduling problem is a classic optimization problem in computer science and operations research. It involves selecting a subset of non-overlapping intervals from a given set, where each interval has an associated weight (or value), such that the sum of the weights of the selected intervals is maximized. This problem is fundamental to understanding greedy algorithms and dynamic programming techniques. It's widely applicable in resource allocation, project management, and scheduling tasks where each task has a duration, a start and end time, and a value or priority.

Who should use it? Anyone involved in planning and resource allocation can benefit from understanding weighted interval scheduling. This includes project managers trying to maximize the value of completed tasks within deadlines, event planners selecting profitable events that don't clash, software developers prioritizing features with the highest impact, and even individuals trying to optimize their personal schedules for maximum productivity or enjoyment.

Common misconceptions about weighted interval scheduling include assuming a simple greedy approach (like always picking the interval with the highest weight first) will yield the optimal solution. While greedy strategies work for some interval problems (like unweighted interval scheduling), the presence of weights often necessitates more sophisticated techniques like dynamic programming to guarantee optimality. Another misconception is that all intervals must be contiguous; the problem specifically deals with selecting non-overlapping intervals, which can be scattered across the timeline.

Weighted Interval Scheduling Formula and Mathematical Explanation

The core of solving the weighted interval scheduling problem lies in dynamic programming. The process typically involves sorting the intervals and then building up a solution iteratively.

Let's define the intervals as $I = \{1, 2, …, n\}$, where each interval $i$ is represented by $(s_i, f_i, w_i)$, denoting its start time, finish time, and weight, respectively.

Step 1: Sort Intervals First, sort all intervals in increasing order of their finish times. Let the sorted intervals be $1, 2, …, n$.

Step 2: Define Predecessor Function For each interval $j$, find the largest index $p(j) < j$ such that interval $j$ and interval $p(j)$ are compatible (i.e., $f_{p(j)} \le s_j$). If no such interval exists, set $p(j) = 0$. This $p(j)$ function helps identify the latest compatible interval before interval $j$.

Step 3: Dynamic Programming Recurrence Let $OPT(j)$ be the maximum weight of a compatible subset of intervals from the first $j$ intervals (in the sorted order). The recurrence relation is: $OPT(j) = \max(w_j + OPT(p(j)), OPT(j-1))$ This means for interval $j$, we have two choices: 1. Include interval $j$: The total weight is $w_j$ plus the maximum weight achievable from intervals compatible with $j$ (which is $OPT(p(j))$). 2. Exclude interval $j$: The total weight is simply the maximum weight achievable from the first $j-1$ intervals ($OPT(j-1)$). We take the maximum of these two choices.

Base Case: $OPT(0) = 0$.

Step 4: Finding the Solution The maximum total weight for the entire set of intervals is $OPT(n)$. To reconstruct the actual set of selected intervals, we can backtrack through the computed $OPT$ values.

Variables Table

Variable Meaning Unit Typical Range
$n$ Number of intervals Count 1 to 50 (for this calculator)
$s_i$ Start time of interval $i$ Time Unit (e.g., hour, day) Non-negative integer
$f_i$ Finish time of interval $i$ Time Unit (e.g., hour, day) Non-negative integer, $f_i \ge s_i$
$w_i$ Weight (value/priority) of interval $i$ Value Unit (e.g., points, dollars) Non-negative number
$p(j)$ Predecessor index for interval $j$ Index 0 to $j-1$
$OPT(j)$ Maximum weight achievable using intervals up to $j$ Value Unit Non-negative number

Practical Examples (Real-World Use Cases)

Example 1: Project Task Prioritization

A software development team needs to select tasks for the next sprint to maximize the overall value delivered. Each task has a start time (when it can begin), a finish time (when it must be completed), and a value score.

  • Intervals: 4 tasks
  • Task 1: Start=1, Finish=3, Weight=5
  • Task 2: Start=2, Finish=5, Weight=6
  • Task 3: Start=4, Finish=6, Weight=5
  • Task 4: Start=6, Finish=7, Weight=4

Calculation: 1. Sort by finish time: Task 1 (f=3), Task 2 (f=5), Task 3 (f=6), Task 4 (f=7). 2. Compute $p(j)$ and $OPT(j)$: – $OPT(0) = 0$ – $OPT(1) = \max(w_1 + OPT(p(1)), OPT(0)) = \max(5 + OPT(0), 0) = 5$. ($p(1)=0$) – $OPT(2) = \max(w_2 + OPT(p(2)), OPT(1))$. $p(2)=0$ since $f_1=3 > s_2=2$. So, $\max(6 + OPT(0), 5) = \max(6, 5) = 6$. – $OPT(3) = \max(w_3 + OPT(p(3)), OPT(2))$. $p(3)=1$ since $f_1=3 \le s_3=4$. So, $\max(5 + OPT(1), 6) = \max(5 + 5, 6) = \max(10, 6) = 10$. – $OPT(4) = \max(w_4 + OPT(p(4)), OPT(3))$. $p(4)=3$ since $f_3=6 \le s_4=6$. So, $\max(4 + OPT(3), 10) = \max(4 + 10, 10) = \max(14, 10) = 14$.

Result: Maximum total weight = 14.

Interpretation: The optimal selection is Task 1 (Weight 5) and Task 3 (Weight 5) and Task 4 (Weight 4). Wait, let's recheck $p(4)$. $p(4)$ should be the latest interval $j'<4$ such that $f_{j'} \le s_4=6$. $f_1=3$, $f_2=5$, $f_3=6$. So $p(4)=3$. $OPT(4) = \max(w_4 + OPT(p(4)), OPT(3)) = \max(4 + OPT(3), OPT(3)) = \max(4+10, 10) = 14$. Let's trace back: $OPT(4)=14$ came from $w_4 + OPT(3)$. So Task 4 is selected. Now consider $OPT(3)=10$. This came from $w_3 + OPT(1)$. So Task 3 is selected. Now consider $OPT(1)=5$. This came from $w_1 + OPT(0)$. So Task 1 is selected. Selected Intervals: Task 1 (1-3, w=5), Task 3 (4-6, w=5), Task 4 (6-7, w=4). Total weight = 5 + 5 + 4 = 14. These are non-overlapping.

Example 2: Event Scheduling

An event venue manager wants to schedule events to maximize revenue. Each potential event has a start time, an end time, and an expected revenue.

  • Intervals: 5 events
  • Event A: Start=9, Finish=12, Revenue=1000
  • Event B: Start=10, Finish=13, Revenue=1200
  • Event C: Start=12, Finish=15, Revenue=1100
  • Event D: Start=14, Finish=17, Revenue=900
  • Event E: Start=16, Finish=18, Revenue=800

Calculation: 1. Sort by finish time: A (f=12), B (f=13), C (f=15), D (f=17), E (f=18). 2. Compute $p(j)$ and $OPT(j)$: – $OPT(0) = 0$ – $OPT(A) = \max(1000 + OPT(0), 0) = 1000$. ($p(A)=0$) – $OPT(B) = \max(1200 + OPT(p(B)), OPT(A))$. $p(B)=0$ since $f_A=12 > s_B=10$. So, $\max(1200 + 0, 1000) = 1200$. – $OPT(C) = \max(1100 + OPT(p(C)), OPT(B))$. $p(C)=A$ since $f_A=12 \le s_C=12$. So, $\max(1100 + OPT(A), 1200) = \max(1100 + 1000, 1200) = \max(2100, 1200) = 2100$. – $OPT(D) = \max(900 + OPT(p(D)), OPT(C))$. $p(D)=C$ since $f_C=15 \le s_D=14$. Wait, $f_C=15$ is NOT $\le s_D=14$. $p(D)$ should be the latest interval $j' 14$. So $p(D)=B$. $\max(900 + OPT(B), 2100) = \max(900 + 1200, 2100) = \max(2100, 2100) = 2100$. – $OPT(E) = \max(800 + OPT(p(E)), OPT(D))$. $p(E)$ should be the latest interval $j'<E$ with $f_{j'} \le s_E=16$. $f_A=12$, $f_B=13$, $f_C=15$, $f_D=17$. So $p(E)=C$. $\max(800 + OPT(C), 2100) = \max(800 + 2100, 2100) = \max(2900, 2100) = 2900$.

Result: Maximum total revenue = 2900.

Interpretation: Trace back: $OPT(E)=2900$ came from $w_E + OPT(C)$. So Event E is selected. Consider $OPT(C)=2100$. This came from $w_C + OPT(A)$. So Event C is selected. Consider $OPT(A)=1000$. This came from $w_A + OPT(0)$. So Event A is selected. Selected Events: Event A (9-12, Rev=1000), Event C (12-15, Rev=1100), Event E (16-18, Rev=800). Total Revenue = 1000 + 1100 + 800 = 2900. These are non-overlapping.

How to Use This Weighted Interval Scheduling Calculator

Using the weighted interval scheduling calculator is straightforward. Follow these steps to find the optimal set of non-overlapping intervals:

  1. Enter Number of Intervals: Start by inputting the total number of intervals (tasks or events) you have.
  2. Input Interval Details: For each interval, you will see fields to enter its Start Time, Finish Time, and Weight. Ensure these values are accurate. Time units should be consistent (e.g., all hours, all days). Weights represent the value or priority of each interval.
  3. Validate Inputs: The calculator performs inline validation. If you enter invalid data (e.g., negative times, finish time before start time, non-numeric values), an error message will appear below the respective field. Correct these errors before proceeding.
  4. Calculate: Click the "Calculate" button. The calculator will process your inputs using the dynamic programming approach.
  5. Review Results: The results section will display:
    • Maximum Total Weight: The highest possible sum of weights from a compatible set of intervals.
    • Number of Selected Intervals: How many intervals are in the optimal set.
    • Selected Intervals List: The IDs of the intervals included in the optimal schedule.
    • Total Weight Explanation: A brief summary of how the total weight was achieved.
  6. Visualize: Examine the generated table and chart. The table lists all intervals and indicates which ones are selected in the optimal solution. The chart provides a visual timeline of the selected intervals.
  7. Copy Results: Use the "Copy Results" button to easily transfer the main result, intermediate values, and key assumptions to another document.
  8. Reset: Click "Reset" to clear all inputs and return to the default settings, allowing you to perform a new calculation.

Decision-Making Guidance: The primary output, Maximum Total Weight, quantifies the best possible outcome given your constraints. Use the list of selected intervals to guide your actual scheduling decisions. If the total weight is lower than expected, review the input factors (weights, timings) to see where improvements can be made.

Key Factors That Affect Weighted Interval Scheduling Results

Several factors significantly influence the outcome of a weighted interval scheduling problem:

  • Interval Weights: This is the most direct factor. Higher weights on intervals naturally increase the potential maximum total weight. Prioritizing tasks with higher intrinsic value is key.
  • Interval Durations ($f_i – s_i$): While not directly in the formula, longer intervals occupy more time, potentially blocking more other intervals. A high-weight, long interval might prevent selecting several smaller, moderately weighted intervals that sum to a greater total.
  • Start and Finish Times ($s_i, f_i$): The precise timing determines compatibility. Intervals that finish early allow more subsequent intervals to be scheduled. Conversely, late-finishing intervals can severely limit options. Scheduling around critical deadlines or resource availability is crucial.
  • Number of Intervals: A larger pool of intervals provides more opportunities for selection, potentially leading to a higher maximum weight. However, it also increases computational complexity.
  • Overlap Patterns: The degree and pattern of overlap among intervals are critical. Dense overlaps with high-weight intervals can create difficult trade-offs. Understanding these patterns helps in identifying potential bottlenecks.
  • Compatibility Constraints: The definition of compatibility (non-overlapping) is fundamental. If compatibility rules were relaxed (e.g., allowing minor overlaps), the problem and its solution would change drastically.
  • Order of Processing: Sorting by finish time is a critical step in the standard dynamic programming solution. Different sorting criteria (e.g., by start time, by weight) would require different algorithms or might not yield an optimal solution.

Frequently Asked Questions (FAQ)

Q1: What is the difference between weighted and unweighted interval scheduling?

A1: In unweighted interval scheduling, the goal is to maximize the *number* of non-overlapping intervals. In weighted interval scheduling, each interval has a value, and the goal is to maximize the *sum of values* of the selected non-overlapping intervals.

Q2: Can a greedy approach solve the weighted interval scheduling problem?

A2: Generally, no. A simple greedy strategy like picking the highest weight interval first, or the earliest finishing interval first, does not guarantee optimality for the weighted version. Dynamic programming is typically required.

Q3: What if multiple intervals have the same finish time?

A3: The sorting order for intervals with the same finish time doesn't affect the correctness of the dynamic programming algorithm, as long as they are processed consistently. You could use start time or weight as a secondary sorting criterion if needed.

Q4: How are the start and finish times measured?

A4: They can be measured in any consistent unit of time (e.g., hours, days, minutes). The key is that the units are consistent across all intervals, and the compatibility check ($f_i \le s_j$) uses these units correctly.

Q5: What does $p(j)=0$ mean in the formula?

A5: $p(j)=0$ signifies that interval $j$ is compatible with no preceding intervals (in the sorted list). This typically happens if interval $j$ starts very early, before any other interval finishes, or if all preceding intervals overlap with it. In the recurrence $OPT(j) = \max(w_j + OPT(p(j)), OPT(j-1))$, $OPT(0)$ is defined as 0, so $w_j + OPT(0)$ simply becomes $w_j$.

Q6: Can this calculator handle negative weights?

A6: This specific calculator implementation assumes non-negative weights, as is standard for most weighted interval scheduling problems focused on maximizing value. Negative weights would complicate the problem significantly and might require different algorithmic approaches.

Q7: How does the calculator reconstruct the set of selected intervals?

A7: After computing the $OPT$ values up to $n$, the calculator backtracks. It starts from $OPT(n)$. If $OPT(n) = w_n + OPT(p(n))$, it means interval $n$ was selected, and it then recursively finds the solution for $OPT(p(n))$. If $OPT(n) = OPT(n-1)$, it means interval $n$ was not selected, and it recursively finds the solution for $OPT(n-1)$. This process continues until $OPT(0)$ is reached.

Q8: What are the time and space complexity of this algorithm?

A8: If intervals are pre-sorted by finish time, the time complexity is O(n log n) due to finding $p(j)$ for each $j$ (can be done efficiently using binary search) and the DP calculation. If sorting is included, it's O(n log n). The space complexity is O(n) to store the $OPT$ values and potentially the $p(j)$ values.

© 2023 Your Financial Website. All rights reserved.
var numIntervalsInput = document.getElementById('numIntervals'); var intervalsInputContainer = document.getElementById('intervalsInputContainer'); var resultsContainer = document.getElementById('resultsContainer'); var maxTotalWeightDisplay = document.getElementById('maxTotalWeight'); var numSelectedIntervalsDisplay = document.getElementById('numSelectedIntervals'); var selectedIntervalsListDisplay = document.getElementById('selectedIntervalsList'); var totalWeightExplanationDisplay = document.getElementById('totalWeightExplanation'); var intervalTableBody = document.getElementById('intervalTable').getElementsByTagName('tbody')[0]; var scheduleChart = document.getElementById('scheduleChart'); var chartInstance = null; function validateInput(inputId, errorId, minValue, maxValue) { var input = document.getElementById(inputId); var errorDisplay = document.getElementById(errorId); var value = parseFloat(input.value); var isValid = true; errorDisplay.style.display = 'none'; input.style.borderColor = '#ddd'; if (isNaN(value)) { errorDisplay.textContent = 'Please enter a valid number.'; errorDisplay.style.display = 'block'; input.style.borderColor = '#dc3545'; isValid = false; } else if (minValue !== undefined && value maxValue) { errorDisplay.textContent = 'Value cannot be greater than ' + maxValue + '.'; errorDisplay.style.display = 'block'; input.style.borderColor = '#dc3545'; isValid = false; } return isValid; } function generateIntervalInputs() { var numIntervals = parseInt(numIntervalsInput.value); intervalsInputContainer.innerHTML = "; // Clear previous inputs if (isNaN(numIntervals) || numIntervals 50) { numIntervals = 50; numIntervalsInput.value = 50; } for (var i = 0; i < numIntervals; i++) { var intervalDiv = document.createElement('div'); intervalDiv.className = 'input-group'; intervalDiv.innerHTML = `
Enter start time, finish time, and weight for Interval ${i + 1}. `; intervalsInputContainer.appendChild(intervalDiv); } } function getIntervalsData() { var intervals = []; var numIntervals = parseInt(numIntervalsInput.value); var allValid = true; for (var i = 0; i < numIntervals; i++) { var startInput = document.getElementById('start_' + i); var finishInput = document.getElementById('finish_' + i); var weightInput = document.getElementById('weight_' + i); var startError = document.getElementById('start_' + i + 'Error'); var finishError = document.getElementById('finish_' + i + 'Error'); var weightError = document.getElementById('weight_' + i + 'Error'); var start = parseFloat(startInput.value); var finish = parseFloat(finishInput.value); var weight = parseFloat(weightInput.value); // Reset errors startError.style.display = 'none'; finishError.style.display = 'none'; weightError.style.display = 'none'; startInput.style.borderColor = '#ddd'; finishInput.style.borderColor = '#ddd'; weightInput.style.borderColor = '#ddd'; var currentValid = true; if (isNaN(start) || start < 0) { startError.textContent = 'Invalid start time.'; startError.style.display = 'block'; startInput.style.borderColor = '#dc3545'; currentValid = false; } if (isNaN(finish) || finish < 0) { finishError.textContent = 'Invalid finish time.'; finishError.style.display = 'block'; finishInput.style.borderColor = '#dc3545'; currentValid = false; } else if (finish = start time.'; finishError.style.display = 'block'; finishInput.style.borderColor = '#dc3545'; currentValid = false; } if (isNaN(weight) || weight < 0) { weightError.textContent = 'Invalid weight.'; weightError.style.display = 'block'; weightInput.style.borderColor = '#dc3545'; currentValid = false; } if (!currentValid) { allValid = false; } intervals.push({ id: i + 1, start: start, finish: finish, weight: weight, selected: false // To be determined by algorithm }); } return allValid ? intervals : null; } function calculateWeightedIntervalScheduling() { var intervalsData = getIntervalsData(); if (!intervalsData) { resultsContainer.style.display = 'none'; return; } // Sort intervals by finish time intervalsData.sort(function(a, b) { return a.finish – b.finish; }); var n = intervalsData.length; var OPT = new Array(n + 1).fill(0); var p = new Array(n).fill(0); // Predecessor array, stores index in sorted array var selectedIntervalsIndices = []; // Stores indices in the *sorted* array // Compute p[j] for each interval j for (var j = 0; j = 0; i–) { if (intervalsData[i].finish <= intervalsData[j].start) { p[j] = i + 1; // Store 1-based index for OPT array break; } } } // Compute OPT values for (var j = 0; j 0) { var currentWeight = intervalsData[j – 1].weight; var predecessorOpt = (p[j] === 0) ? 0 : OPT[p[j]]; if (currentWeight + predecessorOpt >= OPT[j – 1]) { // Interval j-1 (in sorted array) was selected selectedIntervalsIndices.push(j – 1); j = p[j]; // Move to the predecessor's subproblem } else { // Interval j-1 was not selected j = j – 1; // Move to the previous interval's subproblem } } selectedIntervalsIndices.reverse(); // Get them in original order // Map back to original IDs and update interval objects var finalSelectedIntervals = []; var originalIntervalMap = {}; intervalsData.forEach(function(interval, index) { originalIntervalMap[interval.id] = interval; // Use original ID for mapping interval.selected = false; // Reset selection status }); selectedIntervalsIndices.forEach(function(sortedIndex) { var intervalId = intervalsData[sortedIndex].id; if (originalIntervalMap[intervalId]) { originalIntervalMap[intervalId].selected = true; finalSelectedIntervals.push(originalIntervalMap[intervalId]); } }); // Update display maxTotalWeightDisplay.textContent = maxTotalWeight; numSelectedIntervalsDisplay.textContent = `Number of Selected Intervals: ${finalSelectedIntervals.length}`; selectedIntervalsListDisplay.textContent = `Selected Interval IDs: ${finalSelectedIntervals.map(interval => interval.id).join(', ')}`; totalWeightExplanationDisplay.textContent = `Achieved by summing the weights of the selected non-overlapping intervals.`; resultsContainer.style.display = 'block'; updateTableAndChart(intervalsData); // Use the sorted intervalsData for table/chart } function updateTableAndChart(intervalsData) { // Update Table intervalTableBody.innerHTML = "; var selectedCount = 0; intervalsData.forEach(function(interval) { var row = intervalTableBody.insertRow(); row.insertCell(0).textContent = interval.id; row.insertCell(1).textContent = interval.start; row.insertCell(2).textContent = interval.finish; row.insertCell(3).textContent = interval.weight; var selectedCell = row.insertCell(4); selectedCell.textContent = interval.selected ? 'Yes' : 'No'; selectedCell.style.fontWeight = 'bold'; selectedCell.style.color = interval.selected ? 'var(–success-color)' : '#666'; if (interval.selected) { selectedCount++; } }); // Update Chart updateScheduleChart(intervalsData, selectedCount); } function updateScheduleChart(intervalsData, selectedCount) { var ctx = scheduleChart.getContext('2d'); // Destroy previous chart instance if it exists if (chartInstance) { chartInstance.destroy(); } var labels = intervalsData.map(interval => `Interval ${interval.id}`); var weights = intervalsData.map(interval => interval.weight); var selectedWeights = intervalsData.map(interval => interval.selected ? interval.weight : 0); // Determine max time for x-axis scaling var maxTime = 0; intervalsData.forEach(function(interval) { if (interval.finish > maxTime) { maxTime = interval.finish; } }); chartInstance = new Chart(ctx, { type: 'bar', data: { labels: labels, datasets: [{ label: 'Interval Weight', data: weights, backgroundColor: intervalsData.map(interval => interval.selected ? 'rgba(40, 167, 69, 0.7)' : 'rgba(0, 74, 153, 0.5)'), borderColor: intervalsData.map(interval => interval.selected ? 'rgba(40, 167, 69, 1)' : 'rgba(0, 74, 153, 1)'), borderWidth: 1 }, { label: 'Selected Weight Contribution', data: selectedWeights, backgroundColor: 'rgba(255, 193, 7, 0.7)', // Yellowish for selected contribution borderColor: 'rgba(255, 193, 7, 1)', borderWidth: 1 }] }, options: { responsive: true, maintainAspectRatio: false, scales: { x: { title: { display: true, text: 'Intervals (Sorted by Finish Time)' }, grid: { display: false } }, y: { title: { display: true, text: 'Weight' }, beginAtZero: true } }, plugins: { tooltip: { callbacks: { label: function(context) { var label = context.dataset.label || "; if (label) { label += ': '; } if (context.parsed.y !== null) { label += context.parsed.y; } // Add start/finish time info var intervalIndex = context.dataIndex; var interval = intervalsData[intervalIndex]; label += ` (Start: ${interval.start}, Finish: ${interval.finish})`; return label; } } }, legend: { position: 'top', } } } }); } function resetCalculator() { numIntervalsInput.value = 5; generateIntervalInputs(); // Regenerate inputs with defaults // Fill default values for the generated inputs var numIntervals = parseInt(numIntervalsInput.value); for (var i = 0; i 0) { textToCopy += "ID\tStart\tFinish\tWeight\tSelected\n"; for (var i = 0; i < rows.length; i++) { var cells = rows[i].cells; textToCopy += `${cells[0].textContent}\t${cells[1].textContent}\t${cells[2].textContent}\t${cells[3].textContent}\t${cells[4].textContent}\n`; } } // Use a temporary textarea for copying var textArea = document.createElement("textarea"); textArea.value = textToCopy; textArea.style.position = "fixed"; textArea.style.left = "-9999px"; document.body.appendChild(textArea); textArea.focus(); textArea.select(); try { var successful = document.execCommand('copy'); var msg = successful ? 'Results copied successfully!' : 'Failed to copy results.'; // Optionally show a temporary message to the user console.log(msg); alert(msg); } catch (err) { console.error('Unable to copy results.', err); alert('Failed to copy results. Please copy manually.'); } finally { document.body.removeChild(textArea); } } // Initial setup document.addEventListener('DOMContentLoaded', function() { generateIntervalInputs(); resetCalculator(); // Load with default values and calculate }); // Add event listener for number of intervals change numIntervalsInput.addEventListener('change', function() { generateIntervalInputs(); // Clear results and table when number of intervals changes resultsContainer.style.display = 'none'; if (chartInstance) { chartInstance.destroy(); chartInstance = null; } intervalTableBody.innerHTML = ''; }); // Add event listeners for input validation on blur function addValidationListeners() { var numIntervals = parseInt(numIntervalsInput.value); if (isNaN(numIntervals) || numIntervals 50) numIntervals = 50; // Validate numIntervals input numIntervalsInput.addEventListener('blur', function() { validateInput('numIntervals', 'numIntervalsError', 1, 50); }); // Add listeners for generated interval inputs for (var i = 0; i < numIntervals; i++) { var startInput = document.getElementById('start_' + i); var finishInput = document.getElementById('finish_' + i); var weightInput = document.getElementById('weight_' + i); if (startInput) startInput.addEventListener('blur', function() { validateInput(this.id, this.id + 'Error', 0); }); if (finishInput) finishInput.addEventListener('blur', function() { var startVal = parseFloat(document.getElementById('start_' + this.id.split('_')[1]).value); var finishVal = parseFloat(this.value); var errorDisplay = document.getElementById(this.id + 'Error'); errorDisplay.style.display = 'none'; this.style.borderColor = '#ddd'; if (isNaN(finishVal) || finishVal < 0) { errorDisplay.textContent = 'Invalid finish time.'; errorDisplay.style.display = 'block'; this.style.borderColor = '#dc3545'; } else if (!isNaN(startVal) && finishVal = start time.'; errorDisplay.style.display = 'block'; this.style.borderColor = '#dc3545'; } }); if (weightInput) weightInput.addEventListener('blur', function() { validateInput(this.id, this.id + 'Error', 0); }); } } // Re-add listeners after generating inputs var originalGenerate = generateIntervalInputs; generateIntervalInputs = function() { originalGenerate(); addValidationListeners(); }; // Call it once to ensure listeners are added on initial load addValidationListeners();

Leave a Comment