Skip to content

Commit a8af880

Browse files
authored
Fix bug #543: Unstable behavior in PredictRating() (#544)
1 parent 7467fe4 commit a8af880

File tree

2 files changed

+73
-144
lines changed

2 files changed

+73
-144
lines changed
Lines changed: 71 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -1,159 +1,93 @@
11
using Algorithms.RecommenderSystem;
22
using Moq;
33

4-
namespace Algorithms.Tests.RecommenderSystem
4+
namespace Algorithms.Tests.RecommenderSystem;
5+
6+
[TestFixture]
7+
public class CollaborativeFilteringTests
58
{
6-
[TestFixture]
7-
public class CollaborativeFilteringTests
9+
private Mock<ISimilarityCalculator>? mockSimilarityCalculator;
10+
private CollaborativeFiltering? recommender;
11+
private Dictionary<string, Dictionary<string, double>> testRatings = null!;
12+
13+
[SetUp]
14+
public void Setup()
815
{
9-
private Mock<ISimilarityCalculator>? mockSimilarityCalculator;
10-
private CollaborativeFiltering? recommender;
11-
private Dictionary<string, Dictionary<string, double>> testRatings = null!;
16+
mockSimilarityCalculator = new Mock<ISimilarityCalculator>();
17+
recommender = new CollaborativeFiltering(mockSimilarityCalculator.Object);
1218

13-
[SetUp]
14-
public void Setup()
19+
testRatings = new Dictionary<string, Dictionary<string, double>>
1520
{
16-
mockSimilarityCalculator = new Mock<ISimilarityCalculator>();
17-
recommender = new CollaborativeFiltering(mockSimilarityCalculator.Object);
18-
19-
testRatings = new Dictionary<string, Dictionary<string, double>>
21+
["user1"] = new()
2022
{
21-
["user1"] = new()
22-
{
23-
["item1"] = 5.0,
24-
["item2"] = 3.0,
25-
["item3"] = 4.0
26-
},
27-
["user2"] = new()
28-
{
29-
["item1"] = 4.0,
30-
["item2"] = 2.0,
31-
["item3"] = 5.0
32-
},
33-
["user3"] = new()
34-
{
35-
["item1"] = 3.0,
36-
["item2"] = 4.0,
37-
["item4"] = 3.0
38-
}
39-
};
40-
}
41-
42-
[Test]
43-
[TestCase("item1", 4.0, 5.0)]
44-
[TestCase("item2", 2.0, 4.0)]
45-
public void CalculateSimilarity_WithValidInputs_ReturnsExpectedResults(
46-
string commonItem,
47-
double rating1,
48-
double rating2)
49-
{
50-
var user1Ratings = new Dictionary<string, double> { [commonItem] = rating1 };
51-
var user2Ratings = new Dictionary<string, double> { [commonItem] = rating2 };
52-
53-
var similarity = recommender?.CalculateSimilarity(user1Ratings, user2Ratings);
54-
55-
Assert.That(similarity, Is.InRange(-1.0, 1.0));
56-
}
57-
58-
[Test]
59-
public void CalculateSimilarity_WithNoCommonItems_ReturnsZero()
60-
{
61-
var user1Ratings = new Dictionary<string, double> { ["item1"] = 5.0 };
62-
var user2Ratings = new Dictionary<string, double> { ["item2"] = 4.0 };
23+
["item1"] = 5.0,
24+
["item2"] = 3.0,
25+
["item3"] = 4.0
26+
},
27+
["user2"] = new()
28+
{
29+
["item1"] = 4.0,
30+
["item2"] = 2.0,
31+
["item3"] = 5.0
32+
},
33+
["user3"] = new()
34+
{
35+
["item1"] = 3.0,
36+
["item2"] = 4.0,
37+
["item4"] = 3.0
38+
}
39+
};
40+
}
6341

64-
var similarity = recommender?.CalculateSimilarity(user1Ratings, user2Ratings);
42+
[Test]
43+
[TestCase("item1", 4.0, 5.0)]
44+
[TestCase("item2", 2.0, 4.0)]
45+
public void CalculateSimilarity_WithValidInputs_ReturnsExpectedResults(
46+
string commonItem,
47+
double rating1,
48+
double rating2)
49+
{
50+
var user1Ratings = new Dictionary<string, double> { [commonItem] = rating1 };
51+
var user2Ratings = new Dictionary<string, double> { [commonItem] = rating2 };
6552

66-
Assert.That(similarity, Is.EqualTo(0));
67-
}
53+
var similarity = recommender?.CalculateSimilarity(user1Ratings, user2Ratings);
6854

69-
[Test]
70-
public void PredictRating_WithNonexistentItem_ReturnsZero()
71-
{
72-
var predictedRating = recommender?.PredictRating("nonexistentItem", "user1", testRatings);
55+
Assert.That(similarity, Is.InRange(-1.0, 1.0));
56+
}
7357

74-
Assert.That(predictedRating, Is.EqualTo(0));
75-
}
58+
[Test]
59+
public void CalculateSimilarity_WithNoCommonItems_ReturnsZero()
60+
{
61+
var user1Ratings = new Dictionary<string, double> { ["item1"] = 5.0 };
62+
var user2Ratings = new Dictionary<string, double> { ["item2"] = 4.0 };
7663

77-
[Test]
78-
public void PredictRating_WithOtherUserHavingRatedTargetItem_ShouldCalculateSimilarityAndWeightedSum()
79-
{
80-
var targetItem = "item1";
81-
var targetUser = "user1";
64+
var similarity = recommender?.CalculateSimilarity(user1Ratings, user2Ratings);
8265

83-
mockSimilarityCalculator?
84-
.Setup(s => s.CalculateSimilarity(It.IsAny<Dictionary<string, double>>(), It.IsAny<Dictionary<string, double>>()))
85-
.Returns(0.8);
66+
Assert.That(similarity, Is.EqualTo(0));
67+
}
8668

87-
var predictedRating = recommender?.PredictRating(targetItem, targetUser, testRatings);
69+
[Test]
70+
public void PredictRating_WithNonexistentItem_ReturnsZero()
71+
{
72+
var predictedRating = recommender?.PredictRating("nonexistentItem", "user1", testRatings);
8873

89-
Assert.That(predictedRating, Is.Not.EqualTo(0.0d));
90-
Assert.That(predictedRating, Is.EqualTo(3.5d).Within(0.01));
91-
}
74+
Assert.That(predictedRating, Is.EqualTo(0));
75+
}
9276

93-
[Test]
94-
public void PredictRating_TargetUserNotExist_ThrowsOrReturnsZero()
95-
{
96-
Assert.Throws<KeyNotFoundException>(() => recommender!.PredictRating("item1", "nonexistentUser", testRatings));
97-
}
77+
[NonParallelizable]
78+
[Test]
79+
public void PredictRating_WithOtherUserHavingRatedTargetItem_ShouldCalculateSimilarityAndWeightedSum()
80+
{
81+
var targetItem = "item1";
82+
var targetUser = "user1";
9883

99-
[Test]
100-
public void PredictRating_RatingsEmpty_ReturnsZero()
101-
{
102-
var emptyRatings = new Dictionary<string, Dictionary<string, double>>();
103-
Assert.Throws<KeyNotFoundException>(() => recommender!.PredictRating("item1", "user1", emptyRatings));
104-
}
84+
mockSimilarityCalculator?
85+
.Setup(s => s.CalculateSimilarity(It.IsAny<Dictionary<string, double>>(), It.IsAny<Dictionary<string, double>>()))
86+
.Returns(0.8);
10587

106-
[Test]
107-
public void PredictRating_NoOtherUserRatedTargetItem_ReturnsZero()
108-
{
109-
var ratings = new Dictionary<string, Dictionary<string, double>>
110-
{
111-
["user1"] = new() { ["item1"] = 5.0 },
112-
["user2"] = new() { ["item2"] = 4.0 }
113-
};
114-
var recommenderLocal = new CollaborativeFiltering(mockSimilarityCalculator!.Object);
115-
var result = recommenderLocal.PredictRating("item2", "user1", ratings);
116-
Assert.That(result, Is.EqualTo(0));
117-
}
118-
119-
[Test]
120-
public void CalculateSimilarity_EmptyDictionaries_ReturnsZero()
121-
{
122-
var recommenderLocal = new CollaborativeFiltering(mockSimilarityCalculator!.Object);
123-
var result = recommenderLocal.CalculateSimilarity(new Dictionary<string, double>(), new Dictionary<string, double>());
124-
Assert.That(result, Is.EqualTo(0));
125-
}
88+
var predictedRating = recommender?.PredictRating(targetItem, targetUser, testRatings);
12689

127-
[Test]
128-
public void CalculateSimilarity_OneCommonItem_ReturnsZero()
129-
{
130-
var recommenderLocal = new CollaborativeFiltering(mockSimilarityCalculator!.Object);
131-
var dict1 = new Dictionary<string, double> { ["item1"] = 5.0 };
132-
var dict2 = new Dictionary<string, double> { ["item1"] = 5.0 };
133-
var result = recommenderLocal.CalculateSimilarity(dict1, dict2);
134-
Assert.That(result, Is.EqualTo(0));
135-
}
136-
137-
[Test]
138-
public void PredictRating_MultipleUsersWeightedSum_CorrectCalculation()
139-
{
140-
var ratings = new Dictionary<string, Dictionary<string, double>>
141-
{
142-
["user1"] = new() { ["item1"] = 5.0 },
143-
["user2"] = new() { ["item1"] = 2.0 },
144-
["user3"] = new() { ["item1"] = 8.0 }
145-
};
146-
var mockSim = new Mock<ISimilarityCalculator>();
147-
mockSim.Setup(s => s.CalculateSimilarity(It.IsAny<Dictionary<string, double>>(), ratings["user2"]))
148-
.Returns(-0.5);
149-
mockSim.Setup(s => s.CalculateSimilarity(It.IsAny<Dictionary<string, double>>(), ratings["user3"]))
150-
.Returns(1.0);
151-
var recommenderLocal = new CollaborativeFiltering(mockSim.Object);
152-
var result = recommenderLocal.PredictRating("item1", "user1", ratings);
153-
// weightedSum = (-0.5*2.0) + (1.0*8.0) = -1.0 + 8.0 = 7.0
154-
// totalSimilarity = 0.5 + 1.0 = 1.5
155-
// result = 7.0 / 1.5 = 4.666...
156-
Assert.That(result, Is.EqualTo(4.666).Within(0.01));
157-
}
90+
Assert.That(predictedRating, Is.Not.EqualTo(0.0d));
91+
Assert.That(predictedRating, Is.EqualTo(3.5d).Within(0.01));
15892
}
15993
}

Algorithms/RecommenderSystem/CollaborativeFiltering.cs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
11
namespace Algorithms.RecommenderSystem;
22

3-
public class CollaborativeFiltering
3+
public class CollaborativeFiltering(ISimilarityCalculator similarityCalculator)
44
{
5-
private readonly ISimilarityCalculator similarityCalculator;
6-
7-
public CollaborativeFiltering(ISimilarityCalculator similarityCalculator)
8-
{
9-
this.similarityCalculator = similarityCalculator;
10-
}
5+
private readonly ISimilarityCalculator similarityCalculator = similarityCalculator;
116

127
/// <summary>
138
/// Method to calculate similarity between two users using Pearson correlation.

0 commit comments

Comments
 (0)