diff --git a/Algorithms.Tests/Problems/JobScheduling/IntervalSchedulingSolverTests.cs b/Algorithms.Tests/Problems/JobScheduling/IntervalSchedulingSolverTests.cs new file mode 100644 index 00000000..8fda262e --- /dev/null +++ b/Algorithms.Tests/Problems/JobScheduling/IntervalSchedulingSolverTests.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using Algorithms.Problems.JobScheduling; + +namespace Algorithms.Tests.Problems.JobScheduling; + +public class IntervalSchedulingSolverTests +{ + [Test] + public void Schedule_ReturnsEmpty_WhenNoJobs() + { + var result = IntervalSchedulingSolver.Schedule(new List()); + Assert.That(result, Is.Empty); + } + + [Test] + public void Schedule_ReturnsSingleJob_WhenOnlyOneJob() + { + var jobs = new List { new Job(1, 3) }; + var result = IntervalSchedulingSolver.Schedule(jobs); + Assert.That(result, Has.Count.EqualTo(1)); + Assert.That(result[0], Is.EqualTo(jobs[0])); + } + + [Test] + public void Schedule_ThrowsArgumentNullException_WhenJobsIsNull() + { + Assert.Throws(() => IntervalSchedulingSolver.Schedule(jobs: null!)); + } + + [Test] + public void Schedule_SelectsJobsWithEqualEndTime() + { + var jobs = new List + { + new Job(1, 4), + new Job(2, 4), + new Job(4, 6) + }; + var result = IntervalSchedulingSolver.Schedule(jobs); + Assert.That(result, Has.Count.EqualTo(2)); + Assert.That(result, Does.Contain(new Job(1, 4))); + Assert.That(result, Does.Contain(new Job(4, 6))); + } + + [Test] + public void Schedule_SelectsJobStartingAtLastEnd() + { + var jobs = new List + { + new Job(1, 3), + new Job(3, 5), + new Job(5, 7) + }; + var result = IntervalSchedulingSolver.Schedule(jobs); + Assert.That(result, Has.Count.EqualTo(3)); + Assert.That(result[0], Is.EqualTo(jobs[0])); + Assert.That(result[1], Is.EqualTo(jobs[1])); + Assert.That(result[2], Is.EqualTo(jobs[2])); + } + + [Test] + public void Schedule_HandlesJobsWithNegativeTimes() + { + var jobs = new List + { + new Job(-5, -3), + new Job(-2, 1), + new Job(0, 2) + }; + var result = IntervalSchedulingSolver.Schedule(jobs); + Assert.That(result, Has.Count.EqualTo(2)); + Assert.That(result, Does.Contain(new Job(-5, -3))); + Assert.That(result, Does.Contain(new Job(-2, 1))); + } + + [Test] + public void Schedule_SelectsNonOverlappingJobs() + { + var jobs = new List + { + new Job(1, 4), + new Job(3, 5), + new Job(0, 6), + new Job(5, 7), + new Job(8, 9), + new Job(5, 9) + }; + var result = IntervalSchedulingSolver.Schedule(jobs); + // Expected: (1,4), (5,7), (8,9) + Assert.That(result, Has.Count.EqualTo(3)); + Assert.That(result, Does.Contain(new Job(1, 4))); + Assert.That(result, Does.Contain(new Job(5, 7))); + Assert.That(result, Does.Contain(new Job(8, 9))); + } + + [Test] + public void Schedule_HandlesFullyOverlappingJobs() + { + var jobs = new List + { + new Job(1, 5), + new Job(2, 6), + new Job(3, 7) + }; + var result = IntervalSchedulingSolver.Schedule(jobs); + // Only one job can be selected + Assert.That(result, Has.Count.EqualTo(1)); + } +} diff --git a/Algorithms/Problems/JobScheduling/IntervalSchedulingSolver.cs b/Algorithms/Problems/JobScheduling/IntervalSchedulingSolver.cs new file mode 100644 index 00000000..a13a3c76 --- /dev/null +++ b/Algorithms/Problems/JobScheduling/IntervalSchedulingSolver.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Algorithms.Problems.JobScheduling; + +/// +/// Implements the greedy algorithm for Interval Scheduling. +/// Finds the maximum set of non-overlapping jobs. +/// +public static class IntervalSchedulingSolver +{ + /// + /// Returns the maximal set of non-overlapping jobs. + /// + /// List of jobs to schedule. + /// List of selected jobs (maximal set). + public static List Schedule(IEnumerable jobs) + { + if (jobs == null) + { + throw new ArgumentNullException(nameof(jobs)); + } + + // Sort jobs by their end time (earliest finish first) + var sortedJobs = jobs.OrderBy(j => j.End).ToList(); + var result = new List(); + int lastEnd = int.MinValue; + + foreach (var job in sortedJobs) + { + // If the job starts after the last selected job ends, select it + if (job.Start >= lastEnd) + { + result.Add(job); + lastEnd = job.End; + } + } + + return result; + } +} diff --git a/Algorithms/Problems/JobScheduling/Job.cs b/Algorithms/Problems/JobScheduling/Job.cs new file mode 100644 index 00000000..82c095cb --- /dev/null +++ b/Algorithms/Problems/JobScheduling/Job.cs @@ -0,0 +1,6 @@ +namespace Algorithms.Problems.JobScheduling; + +/// +/// Represents a single job with a start and end time. +/// +public record Job(int Start, int End); diff --git a/README.md b/README.md index 37165565..4d92562d 100644 --- a/README.md +++ b/README.md @@ -247,7 +247,8 @@ find more than one implementation for the same objective but using different alg * [Dynamic Programming](./Algorithms/Problems/DynamicProgramming) * [Coin Change](./Algorithms/Problems/DynamicProgramming/CoinChange/DynamicCoinChangeSolver.cs) * [Levenshtein Distance](./Algorithms/Problems/DynamicProgramming/LevenshteinDistance/LevenshteinDistance.cs) - + * [Job Scheduling](./Algorithms/Problems/JobScheduling) + * [Interval Scheduling (Greedy)](./Algorithms/Problems/JobScheduling/IntervalSchedulingSolver.cs) * [Data Structures](./DataStructures) * [Bag](./DataStructures/Bag) * [Bit Array](./DataStructures/BitArray.cs)