Skip to content

Commit 9a0b5f5

Browse files
ngtduc693Duc Nguyen
authored andcommitted
Recommender Systems by User-based Collaborative Filtering
1 parent 36a7dd6 commit 9a0b5f5

File tree

3 files changed

+158
-0
lines changed

3 files changed

+158
-0
lines changed
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
using Algorithms.RecommenderSystem;
2+
using NUnit.Framework;
3+
using System.Collections.Generic;
4+
5+
namespace Algorithms.Tests.RecommenderSystem
6+
{
7+
[TestFixture]
8+
public class CollaborativeFilteringTests
9+
{
10+
private CollaborativeFiltering recommender = new();
11+
private Dictionary<string, Dictionary<string, double>> testRatings = null!;
12+
13+
[SetUp]
14+
public void Setup()
15+
{
16+
recommender = new CollaborativeFiltering();
17+
18+
testRatings = new Dictionary<string, Dictionary<string, double>>
19+
{
20+
["user1"] = new()
21+
{
22+
["item1"] = 5.0,
23+
["item2"] = 3.0,
24+
["item3"] = 4.0
25+
},
26+
["user2"] = new()
27+
{
28+
["item1"] = 4.0,
29+
["item2"] = 2.0,
30+
["item3"] = 5.0
31+
},
32+
["user3"] = new()
33+
{
34+
["item1"] = 3.0,
35+
["item2"] = 4.0,
36+
["item4"] = 3.0
37+
}
38+
};
39+
}
40+
41+
[Test]
42+
[TestCase("item1", 4.0, 5.0)]
43+
[TestCase("item2", 2.0, 4.0)]
44+
public void CalculateSimilarity_WithValidInputs_ReturnsExpectedResults(
45+
string commonItem,
46+
double rating1,
47+
double rating2)
48+
{
49+
var user1Ratings = new Dictionary<string, double> { [commonItem] = rating1 };
50+
var user2Ratings = new Dictionary<string, double> { [commonItem] = rating2 };
51+
52+
var similarity = recommender.CalculateSimilarity(user1Ratings, user2Ratings);
53+
54+
Assert.That(similarity, Is.InRange(-1.0, 1.0));
55+
}
56+
57+
[Test]
58+
public void CalculateSimilarity_WithNoCommonItems_ReturnsZero()
59+
{
60+
var user1Ratings = new Dictionary<string, double> { ["item1"] = 5.0 };
61+
var user2Ratings = new Dictionary<string, double> { ["item2"] = 4.0 };
62+
63+
var similarity = recommender.CalculateSimilarity(user1Ratings, user2Ratings);
64+
65+
Assert.That(similarity, Is.EqualTo(0));
66+
}
67+
68+
[Test]
69+
public void PredictRating_WithNonexistentItem_ReturnsZero()
70+
{
71+
var predictedRating = recommender.PredictRating("nonexistentItem", "user1", testRatings);
72+
73+
Assert.That(predictedRating, Is.EqualTo(0));
74+
}
75+
}
76+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
5+
namespace Algorithms.RecommenderSystem
6+
{
7+
public class CollaborativeFiltering
8+
{
9+
/// <summary>
10+
/// Method to calculate similarity between two users using Pearson correlation.
11+
/// </summary>
12+
/// <param name="user1Ratings">Rating of User 1.</param>
13+
/// <param name="user2Ratings">Rating of User 2.</param>
14+
/// <returns>double value to reflect the index of similarity between two users.</returns>
15+
public double CalculateSimilarity(Dictionary<string, double> user1Ratings, Dictionary<string, double> user2Ratings)
16+
{
17+
var commonItems = user1Ratings.Keys.Intersect(user2Ratings.Keys).ToList();
18+
if (commonItems.Count == 0)
19+
{
20+
return 0;
21+
}
22+
23+
var user1Scores = commonItems.Select(item => user1Ratings[item]).ToArray();
24+
var user2Scores = commonItems.Select(item => user2Ratings[item]).ToArray();
25+
26+
var avgUser1 = user1Scores.Average();
27+
var avgUser2 = user2Scores.Average();
28+
29+
double numerator = 0;
30+
double sumSquare1 = 0;
31+
double sumSquare2 = 0;
32+
33+
for (var i = 0; i < commonItems.Count; i++)
34+
{
35+
var diff1 = user1Scores[i] - avgUser1;
36+
var diff2 = user2Scores[i] - avgUser2;
37+
38+
numerator += diff1 * diff2;
39+
sumSquare1 += diff1 * diff1;
40+
sumSquare2 += diff2 * diff2;
41+
}
42+
43+
var denominator = Math.Sqrt(sumSquare1 * sumSquare2);
44+
return denominator == 0 ? 0 : numerator / denominator;
45+
}
46+
47+
/// <summary>
48+
/// Predict a rating for a specific item by a target user.
49+
/// </summary>
50+
/// <param name="targetItem">The item for which the rating needs to be predicted.</param>
51+
/// <param name="targetUser">The user for whom the rating is being predicted.</param>
52+
/// <param name="ratings">
53+
/// A dictionary containing user ratings where:
54+
/// - The key is the user's identifier (string).
55+
/// - The value is another dictionary where the key is the item identifier (string), and the value is the rating given by the user (double).
56+
/// </param>
57+
/// <returns>The predicted rating for the target item by the target user.
58+
/// If there is insufficient data to predict a rating, the method returns 0.
59+
/// </returns>
60+
public double PredictRating(string targetItem, string targetUser, Dictionary<string, Dictionary<string, double>> ratings)
61+
{
62+
var targetUserRatings = ratings[targetUser];
63+
double totalSimilarity = 0;
64+
double weightedSum = 0;
65+
66+
foreach (var otherUser in ratings.Keys.Where(u => u != targetUser))
67+
{
68+
var otherUserRatings = ratings[otherUser];
69+
if (otherUserRatings.ContainsKey(targetItem))
70+
{
71+
var similarity = CalculateSimilarity(targetUserRatings, otherUserRatings);
72+
totalSimilarity += Math.Abs(similarity);
73+
weightedSum += similarity * otherUserRatings[targetItem];
74+
}
75+
}
76+
77+
return totalSimilarity == 0 ? 0 : weightedSum / totalSimilarity;
78+
}
79+
}
80+
}

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ find more than one implementation for the same objective but using different alg
9696
* [Josephus Problem](./Algorithms/Numeric/JosephusProblem.cs)
9797
* [Newton's Square Root Calculation](./Algorithms/NewtonSquareRoot.cs)
9898
* [SoftMax Function](./Algorithms/Numeric/SoftMax.cs)
99+
* [RecommenderSystem](./Algorithms/RecommenderSystem)
100+
* [CollaborativeFiltering](./Algorithms/RecommenderSystem/CollaborativeFiltering)
99101
* [Searches](./Algorithms/Search)
100102
* [A-Star](./Algorithms/Search/AStar/)
101103
* [Binary Search](./Algorithms/Search/BinarySearcher.cs)

0 commit comments

Comments
 (0)