Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
396 changes: 323 additions & 73 deletions package-lock.json

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ const SlidingWindowVisualizerPage = lazy(
const TwoPointerVisualizerPage = lazy(
() => import('./components/twoPointer/TwoPointerVisualizer')
)
const GreedyVisualizerPage = lazy(
() => import('./components/greedyAlgo/VisualizerPage')
)
const StackVisualizerPage = lazy(
() => import('./components/monotonicStack/StackVisualizerPage')
)
Expand Down Expand Up @@ -261,6 +264,16 @@ const router = createBrowserRouter([
</Suspense>
),
},
{
path: '/greedy',
element: (
<Suspense fallback={<PageLoader />}>
<AppLayout notesKey="algo-notes-greedy">
<GreedyVisualizerPage />
</AppLayout>
</Suspense>
),
},
{
path: '/challenge',
element: (
Expand Down
267 changes: 267 additions & 0 deletions src/algorithms/greedy/fractionalKnapsackSteps.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
const createStep = ({
lineKey,
type,
items,
knapsackState,
activeItemId = null,
message = '',
variables = {},
duration = 1000,
}) => ({
lineKey,
type,
items: structuredClone(items),
knapsackState: structuredClone(knapsackState),
activeItemId,
message,
variables,
duration,
})

export function generateFractionalKnapsackSteps(capacity, inputItems) {
const steps = []
let currentCapacity = Number(capacity)

if (isNaN(currentCapacity) || currentCapacity <= 0) {
currentCapacity = 50
}

// Deep copy the input items
let items = inputItems
.map((item, index) => ({
id: item.id || `Item ${index + 1}`,
value: Number(item.value),
weight: Number(item.weight),
ratio: 0,
status: 'staged', // staged, ratio-calculated, sorted, active, packed, fractioned, skipped
packedRatio: 0,
packedWeight: 0,
packedValue: 0,
}))
.filter(
(item) =>
!isNaN(item.value) &&
!isNaN(item.weight) &&
item.weight > 0 &&
item.value > 0
)
Comment on lines +29 to +47

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add an explicit empty-input guard for consistency & clearer UX.

Nice defensive filtering here! But if every item is invalid (or inputItems is empty), items becomes [] and the generator still emits start → ratios → sort → total → complete with a $0 result and no explanation. Both generateHuffmanSteps and generateJobSequencingSteps short-circuit with a friendly "please add a valid input" step — worth mirroring that here so beginners aren't confused by an empty run.

As per path instructions: "Check for edge cases like empty inputs".

🛡️ Suggested guard
     .filter(
       (item) =>
         !isNaN(item.value) &&
         !isNaN(item.weight) &&
         item.weight > 0 &&
         item.value > 0
     )
+
+  if (items.length === 0) {
+    return [
+      createStep({
+        lineKey: 'init',
+        type: 'start',
+        items: [],
+        knapsackState: {
+          capacityLeft: currentCapacity,
+          totalCapacity: currentCapacity,
+          totalValue: 0,
+          contents: [],
+        },
+        message: 'Please add at least one valid item (positive weight and value).',
+        duration: 1000,
+      }),
+    ]
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Deep copy the input items
let items = inputItems
.map((item, index) => ({
id: item.id || `Item ${index + 1}`,
value: Number(item.value),
weight: Number(item.weight),
ratio: 0,
status: 'staged', // staged, ratio-calculated, sorted, active, packed, fractioned, skipped
packedRatio: 0,
packedWeight: 0,
packedValue: 0,
}))
.filter(
(item) =>
!isNaN(item.value) &&
!isNaN(item.weight) &&
item.weight > 0 &&
item.value > 0
)
// Deep copy the input items
let items = inputItems
.map((item, index) => ({
id: item.id || `Item ${index + 1}`,
value: Number(item.value),
weight: Number(item.weight),
ratio: 0,
status: 'staged', // staged, ratio-calculated, sorted, active, packed, fractioned, skipped
packedRatio: 0,
packedWeight: 0,
packedValue: 0,
}))
.filter(
(item) =>
!isNaN(item.value) &&
!isNaN(item.weight) &&
item.weight > 0 &&
item.value > 0
)
if (items.length === 0) {
return [
createStep({
lineKey: 'init',
type: 'start',
items: [],
knapsackState: {
capacityLeft: currentCapacity,
totalCapacity: currentCapacity,
totalValue: 0,
contents: [],
},
message: 'Please add at least one valid item (positive weight and value).',
duration: 1000,
}),
]
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/algorithms/greedy/fractionalKnapsackSteps.js` around lines 29 - 47, In
the fractional knapsack generator, add an early guard immediately after the
input normalization/filtering that checks whether items is empty, emits a
friendly “please add a valid input” step consistent with generateHuffmanSteps
and generateJobSequencingSteps, and stops further start, ratio, sorting, and
total steps from being generated.

Source: Path instructions


let knapsackState = {
capacityLeft: currentCapacity,
totalCapacity: currentCapacity,
totalValue: 0,
contents: [], // blocks packed inside: { id, weight, value, ratio, color }
}

// Step 1: Start
steps.push(
createStep({
lineKey: 'init',
type: 'start',
items,
knapsackState,
message: `Fractional Knapsack Visualizer started. Knapsack capacity: ${currentCapacity} kg.`,
variables: { capacity: currentCapacity, totalItems: items.length },
duration: 1000,
})
)

// Step 2: Calculate Ratios
items.forEach((item) => {
item.ratio = Number((item.value / item.weight).toFixed(2))
item.status = 'ratio-calculated'
})

steps.push(
createStep({
lineKey: 'ratio',
type: 'calc-ratios',
items,
knapsackState,
message:
'Calculated Value-to-Weight ratio (Value / Weight) for each item. Greedy strategy prioritizes higher ratios.',
variables: { capacity: currentCapacity },
duration: 1200,
})
)

// Step 3: Sort items by ratio descending
items.sort((a, b) => b.ratio - a.ratio || a.weight - b.weight)
items.forEach((item) => {
item.status = 'sorted'
})

steps.push(
createStep({
lineKey: 'sort',
type: 'sort-items',
items,
knapsackState,
message:
'Sorted all items in descending order of their Value-to-Weight ratio.',
variables: { sorted: true },
duration: 1200,
})
)

// Step 4: Initialize total value
steps.push(
createStep({
lineKey: 'total',
type: 'init-total',
items,
knapsackState,
message: 'Initializing total packed value to $0.',
variables: { totalValue: 0, capacityLeft: currentCapacity },
duration: 800,
})
)

// Step 5: Pack greedily
for (let i = 0; i < items.length; i++) {
const item = items[i]
item.status = 'active'

steps.push(
createStep({
lineKey: 'loop',
type: 'process-item',
items,
knapsackState,
activeItemId: item.id,
message: `Inspecting ${item.id} (Weight: ${item.weight} kg, Value: $${item.value}, Ratio: ${item.ratio}).`,
variables: {
activeItem: item.id,
capacityLeft: knapsackState.capacityLeft,
},
duration: 900,
})
)

steps.push(
createStep({
lineKey: 'check',
type: 'check-capacity',
items,
knapsackState,
activeItemId: item.id,
message: `Comparing item weight (${item.weight} kg) with remaining capacity (${knapsackState.capacityLeft} kg).`,
variables: {
itemWeight: item.weight,
capacityLeft: knapsackState.capacityLeft,
},
duration: 900,
})
)

if (knapsackState.capacityLeft >= item.weight) {
// Pack full item
knapsackState.capacityLeft -= item.weight
knapsackState.totalValue += item.value

item.status = 'packed'
item.packedRatio = 1
item.packedWeight = item.weight
item.packedValue = item.value

knapsackState.contents.push({
id: item.id,
weight: item.weight,
value: item.value,
ratio: item.ratio,
fraction: 1,
})

steps.push(
createStep({
lineKey: 'packFull',
type: 'packed-full',
items,
knapsackState,
activeItemId: item.id,
message: `Remaining capacity is sufficient! Packed 100% of ${item.id}. Remaining Capacity reduces to ${knapsackState.capacityLeft} kg.`,
variables: {
totalValue: knapsackState.totalValue,
capacityLeft: knapsackState.capacityLeft,
},
duration: 1200,
})
)
} else if (knapsackState.capacityLeft > 0) {
// Pack fraction of item
const fraction = knapsackState.capacityLeft / item.weight
const fractionVal = Number((item.value * fraction).toFixed(2))
const fractionWt = knapsackState.capacityLeft

knapsackState.totalValue = Number(
(knapsackState.totalValue + fractionVal).toFixed(2)
)
knapsackState.capacityLeft = 0

item.status = 'fractioned'
item.packedRatio = fraction
item.packedWeight = fractionWt
item.packedValue = fractionVal

knapsackState.contents.push({
id: item.id,
weight: fractionWt,
value: fractionVal,
ratio: item.ratio,
fraction: fraction,
})

steps.push(
createStep({
lineKey: 'packFraction',
type: 'packed-fraction',
items,
knapsackState,
activeItemId: item.id,
message: `Remaining capacity is ${fractionWt} kg (less than item weight ${item.weight} kg). Took a fraction: ${(fraction * 100).toFixed(1)}% of ${item.id}. Total value increases by $${fractionVal}.`,
variables: { totalValue: knapsackState.totalValue, capacityLeft: 0 },
duration: 1200,
})
)

// Rest of the items are skipped since knapsack is full
for (let j = i + 1; j < items.length; j++) {
items[j].status = 'skipped'
}
break
} else {
// Capacity is already 0
item.status = 'skipped'
steps.push(
createStep({
lineKey: 'loop',
type: 'process-item',
items,
knapsackState,
activeItemId: item.id,
message: `Knapsack is full. Skipping ${item.id}.`,
variables: { totalValue: knapsackState.totalValue, capacityLeft: 0 },
duration: 800,
})
)
}
}

// Final step
steps.push(
createStep({
lineKey: 'finish',
type: 'complete',
items,
knapsackState,
message: `Fractional Knapsack complete! Maximized total packed value is $${knapsackState.totalValue}.`,
variables: {
finalValue: knapsackState.totalValue,
capacityUsed: knapsackState.totalCapacity - knapsackState.capacityLeft,
},
duration: 1500,
})
)

return steps
}
Loading
Loading