From 6017b94e5bc4eaaac81034e637f9b16b404e94b6 Mon Sep 17 00:00:00 2001 From: Duc Nguyen Date: Sat, 4 Oct 2025 22:59:29 +0700 Subject: [PATCH 1/3] Add Interval Scheduling (Greedy Algorithm) and Tests --- .../IntervalSchedulingSolverTests.cs | 110 ++++++++++++++++++ .../JobScheduling/IntervalSchedulingSolver.cs | 38 ++++++ Algorithms/Problems/JobScheduling/Job.cs | 10 ++ README.md | 4 + 4 files changed, 162 insertions(+) create mode 100644 Algorithms.Tests/Problems/JobScheduling/IntervalSchedulingSolverTests.cs create mode 100644 Algorithms/Problems/JobScheduling/IntervalSchedulingSolver.cs create mode 100644 Algorithms/Problems/JobScheduling/Job.cs 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..e2342652 --- /dev/null +++ b/Algorithms/Problems/JobScheduling/IntervalSchedulingSolver.cs @@ -0,0 +1,38 @@ +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..59a91d1c --- /dev/null +++ b/Algorithms/Problems/JobScheduling/Job.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +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 cb1358d0..c1b76eea 100644 --- a/README.md +++ b/README.md @@ -245,6 +245,10 @@ 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) + * [Weighted Interval Scheduling (DP)](./Algorithms/Problems/JobScheduling/WeightedIntervalSchedulingSolver.cs) + * **Applications:** Work schedule management, room booking, production optimization, and more. * [Data Structures](./DataStructures) * [Bag](./DataStructures/Bag) From 07b91b4f66aaf5d80ef377216cd3c8c365565ab0 Mon Sep 17 00:00:00 2001 From: Duc Nguyen Thanh <58723762+ngtduc693@users.noreply.github.com> Date: Sun, 5 Oct 2025 05:10:16 +0700 Subject: [PATCH 2/3] Add Simple Linear Regression Algorithm (#538) --- .../MachineLearning/LinearRegressionTests.cs | 85 +++++++++++++++ .../MachineLearning/LinearRegression.cs | 100 ++++++++++++++++++ .../JobScheduling/IntervalSchedulingSolver.cs | 4 + Algorithms/Problems/JobScheduling/Job.cs | 4 - README.md | 4 +- 5 files changed, 191 insertions(+), 6 deletions(-) create mode 100644 Algorithms.Tests/MachineLearning/LinearRegressionTests.cs create mode 100644 Algorithms/MachineLearning/LinearRegression.cs diff --git a/Algorithms.Tests/MachineLearning/LinearRegressionTests.cs b/Algorithms.Tests/MachineLearning/LinearRegressionTests.cs new file mode 100644 index 00000000..c43fc41b --- /dev/null +++ b/Algorithms.Tests/MachineLearning/LinearRegressionTests.cs @@ -0,0 +1,85 @@ +using Algorithms.MachineLearning; + +namespace Algorithms.Tests.MachineLearning; + +/// +/// Unit tests for the LinearRegression class. +/// +public class LinearRegressionTests +{ + [Test] + public void Fit_ThrowsException_WhenInputIsNull() + { + var lr = new LinearRegression(); + Assert.Throws(() => lr.Fit(null!, new List { 1 })); + Assert.Throws(() => lr.Fit(new List { 1 }, null!)); + } + + [Test] + public void Fit_ThrowsException_WhenInputIsEmpty() + { + var lr = new LinearRegression(); + Assert.Throws(() => lr.Fit(new List(), new List())); + } + + [Test] + public void Fit_ThrowsException_WhenInputLengthsDiffer() + { + var lr = new LinearRegression(); + Assert.Throws(() => lr.Fit(new List { 1 }, new List { 2, 3 })); + } + + [Test] + public void Fit_ThrowsException_WhenXVarianceIsZero() + { + var lr = new LinearRegression(); + Assert.Throws(() => lr.Fit(new List { 1, 1, 1 }, new List { 2, 3, 4 })); + } + + [Test] + public void Predict_ThrowsException_IfNotFitted() + { + var lr = new LinearRegression(); + Assert.Throws(() => lr.Predict(1.0)); + Assert.Throws(() => lr.Predict(new List { 1.0 })); + } + + [Test] + public void FitAndPredict_WorksForSimpleData() + { + // y = 2x + 1 + var x = new List { 1, 2, 3, 4 }; + var y = new List { 3, 5, 7, 9 }; + var lr = new LinearRegression(); + lr.Fit(x, y); + Assert.That(lr.IsFitted, Is.True); + Assert.That(lr.Intercept, Is.EqualTo(1.0).Within(1e-6)); + Assert.That(lr.Slope, Is.EqualTo(2.0).Within(1e-6)); + Assert.That(lr.Predict(5), Is.EqualTo(11.0).Within(1e-6)); + } + + [Test] + public void FitAndPredict_WorksForNegativeSlope() + { + // y = -3x + 4 + var x = new List { 0, 1, 2 }; + var y = new List { 4, 1, -2 }; + var lr = new LinearRegression(); + lr.Fit(x, y); + Assert.That(lr.Intercept, Is.EqualTo(4.0).Within(1e-6)); + Assert.That(lr.Slope, Is.EqualTo(-3.0).Within(1e-6)); + Assert.That(lr.Predict(3), Is.EqualTo(-5.0).Within(1e-6)); + } + + [Test] + public void Predict_List_WorksCorrectly() + { + var x = new List { 1, 2, 3 }; + var y = new List { 2, 4, 6 }; + var lr = new LinearRegression(); + lr.Fit(x, y); // y = 2x + var predictions = lr.Predict(new List { 4, 5 }); + Assert.That(predictions[0], Is.EqualTo(8.0).Within(1e-6)); + Assert.That(predictions[1], Is.EqualTo(10.0).Within(1e-6)); + } +} diff --git a/Algorithms/MachineLearning/LinearRegression.cs b/Algorithms/MachineLearning/LinearRegression.cs new file mode 100644 index 00000000..4e586376 --- /dev/null +++ b/Algorithms/MachineLearning/LinearRegression.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Algorithms.MachineLearning; + +/// +/// Implements simple linear regression for one independent variable (univariate). +/// Linear regression is a supervised learning algorithm used to model the relationship +/// between a scalar dependent variable (Y) and an independent variable (X). +/// The model fits a line: Y = a + bX, where 'a' is the intercept and 'b' is the slope. +/// +public class LinearRegression +{ + // Intercept (a) and slope (b) of the fitted line + public double Intercept { get; private set; } + + public double Slope { get; private set; } + + public bool IsFitted { get; private set; } + + /// + /// Fits the linear regression model to the provided data. + /// + /// List of independent variable values. + /// List of dependent variable values. + /// Thrown if input lists are null, empty, or of different lengths. + public void Fit(IList x, IList y) + { + if (x == null || y == null) + { + throw new ArgumentException("Input data cannot be null."); + } + + if (x.Count == 0 || y.Count == 0) + { + throw new ArgumentException("Input data cannot be empty."); + } + + if (x.Count != y.Count) + { + throw new ArgumentException("Input lists must have the same length."); + } + + // Calculate means + double xMean = x.Average(); + double yMean = y.Average(); + + // Calculate slope (b) and intercept (a) + double numerator = 0.0; + double denominator = 0.0; + for (int i = 0; i < x.Count; i++) + { + numerator += (x[i] - xMean) * (y[i] - yMean); + denominator += (x[i] - xMean) * (x[i] - xMean); + } + + const double epsilon = 1e-12; + if (Math.Abs(denominator) < epsilon) + { + throw new ArgumentException("Variance of X must not be zero."); + } + + Slope = numerator / denominator; + Intercept = yMean - Slope * xMean; + IsFitted = true; + } + + /// + /// Predicts the output value for a given input using the fitted model. + /// + /// Input value. + /// Predicted output value. + /// Thrown if the model is not fitted. + public double Predict(double x) + { + if (!IsFitted) + { + throw new InvalidOperationException("Model must be fitted before prediction."); + } + + return Intercept + Slope * x; + } + + /// + /// Predicts output values for a list of inputs using the fitted model. + /// + /// List of input values. + /// List of predicted output values. + /// Thrown if the model is not fitted. + public IList Predict(IList xValues) + { + if (!IsFitted) + { + throw new InvalidOperationException("Model must be fitted before prediction."); + } + + return xValues.Select(Predict).ToList(); + } +} diff --git a/Algorithms/Problems/JobScheduling/IntervalSchedulingSolver.cs b/Algorithms/Problems/JobScheduling/IntervalSchedulingSolver.cs index e2342652..a13a3c76 100644 --- a/Algorithms/Problems/JobScheduling/IntervalSchedulingSolver.cs +++ b/Algorithms/Problems/JobScheduling/IntervalSchedulingSolver.cs @@ -1,3 +1,7 @@ +using System; +using System.Collections.Generic; +using System.Linq; + namespace Algorithms.Problems.JobScheduling; /// diff --git a/Algorithms/Problems/JobScheduling/Job.cs b/Algorithms/Problems/JobScheduling/Job.cs index 59a91d1c..82c095cb 100644 --- a/Algorithms/Problems/JobScheduling/Job.cs +++ b/Algorithms/Problems/JobScheduling/Job.cs @@ -1,7 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; - namespace Algorithms.Problems.JobScheduling; /// diff --git a/README.md b/README.md index c1b76eea..66a61b15 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,8 @@ find more than one implementation for the same objective but using different alg * [SoftMax Function](./Algorithms/Numeric/SoftMax.cs) * [RecommenderSystem](./Algorithms/RecommenderSystem) * [CollaborativeFiltering](./Algorithms/RecommenderSystem/CollaborativeFiltering) + * [Machine Learning](./Algorithms/MachineLearning) + * [Linear Regression](./Algorithms/MachineLearning/LinearRegression.cs) * [Searches](./Algorithms/Search) * [A-Star](./Algorithms/Search/AStar/) * [Binary Search](./Algorithms/Search/BinarySearcher.cs) @@ -247,8 +249,6 @@ find more than one implementation for the same objective but using different alg * [Levenshtein Distance](./Algorithms/Problems/DynamicProgramming/LevenshteinDistance/LevenshteinDistance.cs) * [Job Scheduling](./Algorithms/Problems/JobScheduling) * [Interval Scheduling (Greedy)](./Algorithms/Problems/JobScheduling/IntervalSchedulingSolver.cs) - * [Weighted Interval Scheduling (DP)](./Algorithms/Problems/JobScheduling/WeightedIntervalSchedulingSolver.cs) - * **Applications:** Work schedule management, room booking, production optimization, and more. * [Data Structures](./DataStructures) * [Bag](./DataStructures/Bag) From b9ce9b5304dc43ce6a61d8ddfae5b5d01ce350ad Mon Sep 17 00:00:00 2001 From: Duc Nguyen Date: Sun, 5 Oct 2025 09:44:31 +0700 Subject: [PATCH 3/3] Update the Readme.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 66a61b15..4d92562d 100644 --- a/README.md +++ b/README.md @@ -249,7 +249,6 @@ find more than one implementation for the same objective but using different alg * [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)