Skip to content

Commit 7b4fc36

Browse files
committed
Merge branch 'master' into bugfix/unstable-behavior-in-the-predict-logic-test
2 parents 6062765 + 7467fe4 commit 7b4fc36

19 files changed

+614
-37
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using NUnit.Framework;
2+
using Algorithms.MachineLearning;
3+
using System;
4+
5+
namespace Algorithms.Tests.MachineLearning;
6+
7+
[TestFixture]
8+
public class LogisticRegressionTests
9+
{
10+
[Test]
11+
public void Fit_ThrowsOnEmptyInput()
12+
{
13+
var model = new LogisticRegression();
14+
Assert.Throws<ArgumentException>(() => model.Fit(Array.Empty<double[]>(), Array.Empty<int>()));
15+
}
16+
17+
[Test]
18+
public void Fit_ThrowsOnMismatchedLabels()
19+
{
20+
var model = new LogisticRegression();
21+
double[][] X = { new double[] { 1, 2 } };
22+
int[] y = { 1, 0 };
23+
Assert.Throws<ArgumentException>(() => model.Fit(X, y));
24+
}
25+
26+
[Test]
27+
public void FitAndPredict_WorksOnSimpleData()
28+
{
29+
// Simple AND logic
30+
double[][] X =
31+
{
32+
new[] { 0.0, 0.0 },
33+
new[] { 0.0, 1.0 },
34+
new[] { 1.0, 0.0 },
35+
new[] { 1.0, 1.0 }
36+
};
37+
int[] y = { 0, 0, 0, 1 };
38+
var model = new LogisticRegression();
39+
model.Fit(X, y, epochs: 2000, learningRate: 0.1);
40+
Assert.That(model.Predict(new double[] { 0, 0 }), Is.EqualTo(0));
41+
Assert.That(model.Predict(new double[] { 0, 1 }), Is.EqualTo(0));
42+
Assert.That(model.Predict(new double[] { 1, 0 }), Is.EqualTo(0));
43+
Assert.That(model.Predict(new double[] { 1, 1 }), Is.EqualTo(1));
44+
}
45+
46+
[Test]
47+
public void PredictProbability_ThrowsOnFeatureMismatch()
48+
{
49+
var model = new LogisticRegression();
50+
double[][] X = { new double[] { 1, 2 } };
51+
int[] y = { 1 };
52+
model.Fit(X, y);
53+
Assert.Throws<ArgumentException>(() => model.PredictProbability(new double[] { 1 }));
54+
}
55+
56+
[Test]
57+
public void FeatureCount_ReturnsCorrectValue()
58+
{
59+
var model = new LogisticRegression();
60+
double[][] X = { new double[] { 1, 2, 3 } };
61+
int[] y = { 1 };
62+
model.Fit(X, y);
63+
Assert.That(model.FeatureCount, Is.EqualTo(3));
64+
}
65+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using Algorithms.Problems.JobScheduling;
4+
5+
namespace Algorithms.Tests.Problems.JobScheduling;
6+
7+
public class IntervalSchedulingSolverTests
8+
{
9+
[Test]
10+
public void Schedule_ReturnsEmpty_WhenNoJobs()
11+
{
12+
var result = IntervalSchedulingSolver.Schedule(new List<Job>());
13+
Assert.That(result, Is.Empty);
14+
}
15+
16+
[Test]
17+
public void Schedule_ReturnsSingleJob_WhenOnlyOneJob()
18+
{
19+
var jobs = new List<Job> { new Job(1, 3) };
20+
var result = IntervalSchedulingSolver.Schedule(jobs);
21+
Assert.That(result, Has.Count.EqualTo(1));
22+
Assert.That(result[0], Is.EqualTo(jobs[0]));
23+
}
24+
25+
[Test]
26+
public void Schedule_ThrowsArgumentNullException_WhenJobsIsNull()
27+
{
28+
Assert.Throws<ArgumentNullException>(() => IntervalSchedulingSolver.Schedule(jobs: null!));
29+
}
30+
31+
[Test]
32+
public void Schedule_SelectsJobsWithEqualEndTime()
33+
{
34+
var jobs = new List<Job>
35+
{
36+
new Job(1, 4),
37+
new Job(2, 4),
38+
new Job(4, 6)
39+
};
40+
var result = IntervalSchedulingSolver.Schedule(jobs);
41+
Assert.That(result, Has.Count.EqualTo(2));
42+
Assert.That(result, Does.Contain(new Job(1, 4)));
43+
Assert.That(result, Does.Contain(new Job(4, 6)));
44+
}
45+
46+
[Test]
47+
public void Schedule_SelectsJobStartingAtLastEnd()
48+
{
49+
var jobs = new List<Job>
50+
{
51+
new Job(1, 3),
52+
new Job(3, 5),
53+
new Job(5, 7)
54+
};
55+
var result = IntervalSchedulingSolver.Schedule(jobs);
56+
Assert.That(result, Has.Count.EqualTo(3));
57+
Assert.That(result[0], Is.EqualTo(jobs[0]));
58+
Assert.That(result[1], Is.EqualTo(jobs[1]));
59+
Assert.That(result[2], Is.EqualTo(jobs[2]));
60+
}
61+
62+
[Test]
63+
public void Schedule_HandlesJobsWithNegativeTimes()
64+
{
65+
var jobs = new List<Job>
66+
{
67+
new Job(-5, -3),
68+
new Job(-2, 1),
69+
new Job(0, 2)
70+
};
71+
var result = IntervalSchedulingSolver.Schedule(jobs);
72+
Assert.That(result, Has.Count.EqualTo(2));
73+
Assert.That(result, Does.Contain(new Job(-5, -3)));
74+
Assert.That(result, Does.Contain(new Job(-2, 1)));
75+
}
76+
77+
[Test]
78+
public void Schedule_SelectsNonOverlappingJobs()
79+
{
80+
var jobs = new List<Job>
81+
{
82+
new Job(1, 4),
83+
new Job(3, 5),
84+
new Job(0, 6),
85+
new Job(5, 7),
86+
new Job(8, 9),
87+
new Job(5, 9)
88+
};
89+
var result = IntervalSchedulingSolver.Schedule(jobs);
90+
// Expected: (1,4), (5,7), (8,9)
91+
Assert.That(result, Has.Count.EqualTo(3));
92+
Assert.That(result, Does.Contain(new Job(1, 4)));
93+
Assert.That(result, Does.Contain(new Job(5, 7)));
94+
Assert.That(result, Does.Contain(new Job(8, 9)));
95+
}
96+
97+
[Test]
98+
public void Schedule_HandlesFullyOverlappingJobs()
99+
{
100+
var jobs = new List<Job>
101+
{
102+
new Job(1, 5),
103+
new Job(2, 6),
104+
new Job(3, 7)
105+
};
106+
var result = IntervalSchedulingSolver.Schedule(jobs);
107+
// Only one job can be selected
108+
Assert.That(result, Has.Count.EqualTo(1));
109+
}
110+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
using Algorithms.Problems.TravelingSalesman;
2+
using NUnit.Framework;
3+
4+
namespace Algorithms.Tests.Problems.TravelingSalesman;
5+
6+
/// <summary>
7+
/// Unit tests for TravelingSalesmanSolver. Covers brute-force and nearest neighbor methods, including edge cases and invalid input.
8+
/// </summary>
9+
[TestFixture]
10+
public class TravelingSalesmanSolverTests
11+
{
12+
/// <summary>
13+
/// Tests brute-force TSP solver on a 4-city symmetric distance matrix with known optimal route.
14+
/// </summary>
15+
[Test]
16+
public void SolveBruteForce_KnownOptimalRoute_ReturnsCorrectResult()
17+
{
18+
// Distance matrix for 4 cities (symmetric, triangle inequality holds)
19+
double[,] matrix =
20+
{
21+
{ 0, 10, 15, 20 },
22+
{ 10, 0, 35, 25 },
23+
{ 15, 35, 0, 30 },
24+
{ 20, 25, 30, 0 }
25+
};
26+
var (route, distance) = TravelingSalesmanSolver.SolveBruteForce(matrix);
27+
// Optimal route: 0 -> 1 -> 3 -> 2 -> 0, total distance = 80
28+
Assert.That(distance, Is.EqualTo(80));
29+
Assert.That(route, Is.EquivalentTo(new[] { 0, 1, 3, 2, 0 }));
30+
}
31+
32+
/// <summary>
33+
/// Tests nearest neighbor heuristic on the same matrix. May not be optimal.
34+
/// </summary>
35+
[Test]
36+
public void SolveNearestNeighbor_Heuristic_ReturnsFeasibleRoute()
37+
{
38+
double[,] matrix =
39+
{
40+
{ 0, 10, 15, 20 },
41+
{ 10, 0, 35, 25 },
42+
{ 15, 35, 0, 30 },
43+
{ 20, 25, 30, 0 }
44+
};
45+
var (route, distance) = TravelingSalesmanSolver.SolveNearestNeighbor(matrix, 0);
46+
// Route: 0 -> 1 -> 3 -> 2 -> 0, total distance = 80
47+
Assert.That(route.Length, Is.EqualTo(5));
48+
Assert.That(route.First(), Is.EqualTo(0));
49+
Assert.That(route.Last(), Is.EqualTo(0));
50+
Assert.That(distance, Is.GreaterThanOrEqualTo(80)); // Heuristic may be optimal or suboptimal
51+
}
52+
53+
/// <summary>
54+
/// Tests nearest neighbor with invalid start index.
55+
/// </summary>
56+
[Test]
57+
public void SolveNearestNeighbor_InvalidStart_ThrowsException()
58+
{
59+
double[,] matrix =
60+
{
61+
{ 0, 1 },
62+
{ 1, 0 }
63+
};
64+
Assert.Throws<ArgumentOutOfRangeException>(() => TravelingSalesmanSolver.SolveNearestNeighbor(matrix, -1));
65+
Assert.Throws<ArgumentOutOfRangeException>(() => TravelingSalesmanSolver.SolveNearestNeighbor(matrix, 2));
66+
}
67+
68+
/// <summary>
69+
/// Tests nearest neighbor when no unvisited cities remain (should throw InvalidOperationException).
70+
/// </summary>
71+
[Test]
72+
public void SolveNearestNeighbor_NoUnvisitedCities_ThrowsException()
73+
{
74+
// Construct a matrix where one city cannot be reached (simulate unreachable city)
75+
double[,] matrix =
76+
{
77+
{ 0, double.MaxValue, 1 },
78+
{ double.MaxValue, 0, double.MaxValue },
79+
{ 1, double.MaxValue, 0 }
80+
};
81+
// Start at city 0, city 1 is unreachable from both 0 and 2
82+
Assert.Throws<InvalidOperationException>(() => TravelingSalesmanSolver.SolveNearestNeighbor(matrix, 0));
83+
}
84+
85+
/// <summary>
86+
/// Tests brute-force and nearest neighbor with non-square matrix.
87+
/// </summary>
88+
[Test]
89+
public void NonSquareMatrix_ThrowsException()
90+
{
91+
double[,] matrix = new double[2, 3];
92+
Assert.Throws<ArgumentException>(() => TravelingSalesmanSolver.SolveBruteForce(matrix));
93+
Assert.Throws<ArgumentException>(() => TravelingSalesmanSolver.SolveNearestNeighbor(matrix, 0));
94+
}
95+
96+
/// <summary>
97+
/// Tests brute-force with less than two cities (invalid case).
98+
/// </summary>
99+
[Test]
100+
public void SolveBruteForce_TooFewCities_ThrowsException()
101+
{
102+
double[,] matrix = { { 0 } };
103+
Assert.Throws<ArgumentException>(() => TravelingSalesmanSolver.SolveBruteForce(matrix));
104+
}
105+
106+
/// <summary>
107+
/// Tests nearest neighbor with only two cities (trivial case).
108+
/// </summary>
109+
[Test]
110+
public void SolveNearestNeighbor_TwoCities_ReturnsCorrectRoute()
111+
{
112+
double[,] matrix =
113+
{
114+
{ 0, 5 },
115+
{ 5, 0 }
116+
};
117+
var (route, distance) = TravelingSalesmanSolver.SolveNearestNeighbor(matrix, 0);
118+
Assert.That(route, Is.EquivalentTo(new[] { 0, 1, 0 }));
119+
Assert.That(distance, Is.EqualTo(10));
120+
}
121+
}

Algorithms.Tests/Sequences/DivisorsCountSequenceTests.cs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,17 @@ public class DivisorsCountSequenceTests
88
public void First10ElementsCorrect()
99
{
1010
// These values are taken from https://oeis.org/A000005 for comparison.
11-
var oeisSource = new BigInteger[]
12-
{
13-
1, 2, 2, 3, 2, 4, 2, 4, 3, 4, 2, 6, 2,
14-
4, 4, 5, 2, 6, 2, 6, 4, 4, 2, 8, 3, 4,
15-
4, 6, 2, 8, 2, 6, 4, 4, 4, 9, 2, 4, 4,
16-
8, 2, 8, 2, 6, 6, 4, 2, 10, 3, 6, 4, 6,
17-
2, 8, 4, 8, 4, 4, 2, 12, 2, 4, 6, 7, 4,
18-
8, 2, 6, 4, 8, 2, 12, 2, 4, 6, 6, 4, 8,
19-
2, 10, 5, 4, 2, 12, 4, 4, 4, 8, 2, 12, 4,
20-
6, 4, 4, 4, 12, 2, 6, 6, 9, 2, 8, 2, 8,
21-
};
11+
BigInteger[] oeisSource =
12+
[
13+
1, 2, 2, 3, 2, 4, 2, 4, 3, 4, 2, 6, 2,
14+
4, 4, 5, 2, 6, 2, 6, 4, 4, 2, 8, 3, 4,
15+
4, 6, 2, 8, 2, 6, 4, 4, 4, 9, 2, 4, 4,
16+
8, 2, 8, 2, 6, 6, 4, 2, 10, 3, 6, 4, 6,
17+
2, 8, 4, 8, 4, 4, 2, 12, 2, 4, 6, 7, 4,
18+
8, 2, 6, 4, 8, 2, 12, 2, 4, 6, 6, 4, 8,
19+
2, 10, 5, 4, 2, 12, 4, 4, 4, 8, 2, 12, 4,
20+
6, 4, 4, 4, 12, 2, 6, 6, 9, 2, 8, 2, 8,
21+
];
2222

2323
var sequence = new DivisorsCountSequence().Sequence.Take(oeisSource.Length);
2424
sequence.SequenceEqual(oeisSource).Should().BeTrue();

Algorithms.Tests/Sequences/EulerTotientSequenceTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ public void FirstElementsCorrect()
1111
// Initial test of 69 number from table at https://oeis.org/A000010/list and passed test.
1212
// Extended out to 500 values from https://primefan.tripod.com/Phi500.html and passed initial 69
1313
// along with remaining values.
14-
var check = new BigInteger[]
15-
{
14+
BigInteger[] check =
15+
[
1616
1, 1, 2, 2, 4, 2, 6, 4, 6, 4, 10, 4, 12, 6, 8, 8, 16, 6, 18, 8,
1717
12, 10, 22, 8, 20, 12, 18, 12, 28, 8, 30, 16, 20, 16, 24, 12, 36, 18, 24, 16,
1818
40, 12, 42, 20, 24, 22, 46, 16, 42, 20, 32, 24, 52, 18, 40, 24, 36, 28, 58, 16,
@@ -38,7 +38,7 @@ public void FirstElementsCorrect()
3838
252, 192, 442, 144, 352, 222, 296, 192, 448, 120, 400, 224, 300, 226, 288, 144, 456, 228, 288, 176,
3939
460, 120, 462, 224, 240, 232, 466, 144, 396, 184, 312, 232, 420, 156, 360, 192, 312, 238, 478, 128,
4040
432, 240, 264, 220, 384, 162, 486, 240, 324, 168, 490, 160, 448, 216, 240, 240, 420, 164, 498, 200,
41-
};
41+
];
4242

4343
var sequence = new EulerTotientSequence().Sequence.Take(check.Length);
4444
sequence.SequenceEqual(check).Should().BeTrue();

Algorithms.Tests/Sequences/FibonacciSequenceTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ public class FibonacciSequenceTests
88
public void First10ElementsCorrect()
99
{
1010
var sequence = new FibonacciSequence().Sequence.Take(10);
11-
sequence.SequenceEqual(new BigInteger[] { 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 })
11+
BigInteger[] expected = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34];
12+
sequence.SequenceEqual(expected)
1213
.Should().BeTrue();
1314
}
1415
}

Algorithms.Tests/Sequences/KolakoskiSequenceTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ public class KolakoskiSequenceTests
88
public void First100ElementsCorrect()
99
{
1010
// Taken from https://oeis.org/A000002
11-
var expected = new BigInteger[]
12-
{
11+
BigInteger[] expected =
12+
[
1313
1, 2, 2, 1, 1, 2, 1, 2, 2, 1,
1414
2, 2, 1, 1, 2, 1, 1, 2, 2, 1,
1515
2, 1, 1, 2, 1, 2, 2, 1, 1, 2,
@@ -20,7 +20,7 @@ public void First100ElementsCorrect()
2020
1, 2, 1, 2, 2, 1, 2, 1, 1, 2,
2121
2, 1, 2, 2, 1, 1, 2, 1, 2, 2,
2222
1, 2, 2, 1, 1, 2, 1, 1, 2, 2,
23-
};
23+
];
2424

2525
var sequence = new KolakoskiSequence().Sequence.Take(100);
2626
var sequence2 = new KolakoskiSequence2().Sequence.Take(100);

Algorithms.Tests/Sequences/NaturalSequenceTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ public class NaturalSequenceTests
88
public void First10ElementsCorrect()
99
{
1010
var sequence = new NaturalSequence().Sequence.Take(10);
11-
sequence.SequenceEqual(new BigInteger[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 })
11+
BigInteger[] expected = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
12+
sequence.SequenceEqual(expected)
1213
.Should().BeTrue();
1314
}
1415
}

Algorithms.Tests/Sequences/NegativeIntegersSequenceTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ public class NegativeIntegersSequenceTests
88
public void First10ElementsCorrect()
99
{
1010
var sequence = new NegativeIntegersSequence().Sequence.Take(10);
11-
sequence.SequenceEqual(new BigInteger[] { -1, -2, -3, -4, -5, -6, -7, -8, -9, -10 })
11+
BigInteger[] expected = [-1, -2, -3, -4, -5, -6, -7, -8, -9, -10];
12+
sequence.SequenceEqual(expected)
1213
.Should().BeTrue();
1314
}
1415
}

0 commit comments

Comments
 (0)