我正在研究一种计算餐馆间相似系数的算法。在我们能够计算出所述相似度之前,我需要3位对这两家餐馆进行评分的用户。这是一个以可能的方案为例的表:
| Restaurant 1 | Restaurant 2
User 1 | X | 2
User 2 | 1 | 5
User 3 | 4 | 3
User 4 | 2 | 1
User 5 | X | 5
此处X
代表不予审核,评分为用户对餐厅的评价。您可以看到它可以计算相似度,因为用户2,3和4对两家餐馆都进行了评分。
因为我使用的是adjusted cosine similarity,所以我需要每个用户的平均评分。
现在我正在检索所有餐馆的列表和一个双循环,以检查是否可以计算餐馆之间的相似性。
我使用以下双循环来检查它是否可能:
for (int i = 0; i < allRestaurants.Count; i++)
for (int j = 0; j < allRestaurants.Count; j++)
if (i < j)
matrix.Add(new Similarity()
{
Id = Guid.NewGuid(),
FirstRest = allRestaurants[i],
SecondRest = allRestaurants[j],
Sim = ComputeSimilarity(allRestaurants[i], allRestaurants[j], allReviews)
});
在ComputeSimilarity
内部我使用以下LINQ语句来检查&#39;匹配的数量&#39;:
public double ComputeSimilarity(Guid restaurant1, Guid restaurant2, IEnumerable<Tuple<List<Review>, double>> allReviews)
{ //The double in the list of allReviews is the average rating of the user.
var matches = (from R1 in allReviews.SelectMany(x => x.Item1).Where(x => x.RestaurantId == subject1)
from R2 in allReviews.SelectMany(x => x.Item1).Where(x => x.RestaurantId == subject2)
where R1.UserId == R2.UserId
select Tuple.Create(R1, R2, allReviews.Where(x => x.Item1.FirstOrDefault().UserId == R1.UserId)
.Select(x => x.Item2)
.FirstOrDefault()))
.DistinctBy(x => x.Item1.UserId);
int amountOfMatches = matches.Count(); //Don't mind this, not looking for performance here at the moment.
if (amountOfMatches < 4)
return 0;
现在你可以看到这种方法非常重要,当你增加double for循环的餐馆数量时,需要很多的时间。
我认为更好的方法是检索所有已满足此要求的餐馆,但我仍然坚持如何做到这一点。我想你可以找回一系列的比赛&#39;这将是一个看起来像这样的元组列表:Tuple<Review, Review, double>
。这些评论来自同一用户,而double是来自用户的评论的平均评分。
我一直在尝试多次尝试,但是当我想添加只需要通过3场比赛检索餐馆的条件时,我一直陷入困境。
作为参考,我的评论对象如下所示:
public class Review
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public virtual Guid Id { get; set; }
public virtual int Rating { get; set; }
public virtual Guid RestaurantId { get; set; }
public virtual Guid UserId { get; set; }
//More irrelevant attributes here
}
我的餐厅对象:
public class Restaurant
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public virtual Guid Id { get; set; }
//More irrelevant attributes here
}
我正在寻找比我目前的方法表现更好的东西,是否有人可以指出我正确的方向或建议更好的方法?另外,如果您需要更多信息,请告诉我们!提前谢谢!
编辑:第一个示例显示了两家餐馆,但当然列表可能更大。关键在于我只想要能够计算相似度的餐馆。
所以请看下面的例子:
| Restaurant 1 | Restaurant 2 | Restaurant 3
User 1 | X | 2 | X
User 2 | 1 | 5 | X
User 3 | 4 | 3 | 3
User 4 | 2 | 1 | 2
User 5 | X | 5 | X
User 6 | X | X | 2
唯一可能的匹配是在餐厅1和餐馆2之间。由于没有足够的匹配(在这种情况下至少为3),因此无法计算相似性。因此,优化此方法的方法是创建一个餐馆列表, 可以计算相似度。
为了进一步解释,match
是两个用户评价两家餐馆的地方。 Restaurant 3
有3条评论,但其中只有2条是匹配,因为User 6
只对该餐厅进行了评分。
因此,如果我们将上面的3家餐厅作为输入,它应该只创建一个可以计算相似性的餐馆列表(在这种情况下只有餐馆1和2)。
编辑2 我将添加一个示例,说明我希望的输出应该如何显示:
A&#39;匹配&#39;至少有3个用户评价相同的2家餐厅。所以,我们假设我们有餐厅X和Y,输出可能如下:
| Restaurant X | Restaurant Y
User 1 | 5 | 3
User 2 | 2 | 5
User 3 | 1 | 2
现在,如果我们在列表中添加了第三家餐厅,每个用户也已经审核过:
| Restaurant X | Restaurant Y | Restaurant Z
User 1 | 5 | 3 | 2
User 2 | 2 | 5 | 3
User 3 | 1 | 2 | 1
现在你可以看到它可以在这里产生每个餐厅之间的相似性。 X和Y,X和Z,Y和Z之间的相似性。
这可以在一个单独的类中建模,如下所示:
public class Match
{
public Review rev1 { get; set; } //These two reviews have been left by the same users, on separate restaurants.
public Review rev2 { get; set; }
}
如果我们有3个匹配项,其中每个对象与rev1相同RestaurantId
,而rev2则相同RestaurantId
。
所以这些匹配的列表可能如下所示:
rev1.RestaurantId = 1
| rev2.RestaurantId = 2
| UserId = 11
此用户ID在rev1和rev2上是相同的rev1.RestaurantId = 1
| rev2.RestaurantId = 2
| UserId = 12
此用户ID在rev1和rev2上是相同的rev1.RestaurantId = 1
| rev2.RestaurantId = 2
| UserId = 13
此用户ID在rev1和rev2上是相同的我知道ids是guids,但这纯粹就是一个例子。
我希望这是有道理的。
答案 0 :(得分:3)
我认为我已经完成了你想要达到的目标。
我已在您的帖子中建立了一个包含评论表的数据库,并且我将您在修改中显示的表格设置为相同的数据。
第1步
所以,我首先通过RestaurantId将值列为该餐厅评价用户的所有评论列表。
它给了我们这个:
第2步
排除对评论少于2名余额的用户评分少于3次的餐馆。
它给了我们这个:
第3步
我们现在有正确的餐馆列表,但我们需要排除不在比赛中的用户评论。然后把所有这些都放在餐馆和评论中。
它给了我们这个,这是最终结果:
以下是代码:
var matches = this.Reviews.GroupBy(r => r.RestaurantId, r => this.Reviews.Where(rr => rr.UserId == r.UserId))
.ToList()
.Where(g => g.Where(gg => gg.Count() >= 2).Count() >= 3);
var matchingReviewsByRestaurant = matches.ToDictionary(m => m.Key, m => m.Where(g => g.Count() >= 2).SelectMany(g => g));
我希望它是你想要的!
最终答案,所以这就是你想要的,用户的评论对。
// Step 1 : Get the right reviews
var matches = this.Reviews.GroupBy(r => r.RestaurantId, r => this.Reviews.Where(rr => rr.UserId == r.UserId)).ToList()
.Where(g => g.Where(gg => gg.Count() >= 2).Count() >= 3);
var matchingReviewsByRestaurant = matches.ToDictionary(m => m.Key, m => m.Where(g => g.Count() >= 2).SelectMany(g => g));
// Step 2 : Create the matching couples
var reviewsByUsers = matchingReviewsByRestaurant.SelectMany(m => m.Value).Distinct().ToLookup(r => r.UserId);
var matchingReviewsCouples = new List<Match>();
foreach (var reviews in reviewsByUsers)
{
var combinations = reviews.SelectMany(x => reviews, (x, y) => new Match(x, y))
.Where(m => m.Review1.Id.CompareTo(m.Review2.Id) > 0)
.ToList();
matchingReviewsCouples.AddRange(combinations);
}
// Final Results are in matchingReviewsCouples
使用我的例子的数据,结果如下:
答案 1 :(得分:-2)
我会建议以下; , 1)如果你没有大量的用户和餐馆,重新考虑使用Guid.Make id值int.Query类型的查询更快。
2)您的输入数据只是int。您可以违反数据冗余以加快查询速度。您可以找到我的数据模型。
<Similarity>
<Users>
<User id="25">
<Restaurants>
<Restaurant id="1" rating="1"/>
<Restaurant id="2" rating="2"/>
</Restaurants>
</User>
<User id="26">
<Restaurants>
<Restaurant id="1" rating="3"/>
<Restaurant id="2" rating="5"/>
</Restaurants>
</User>
</Users>
<Restaurants>
<Restaurant id="1">
<Users>
<User id="25" rating="1"/>
<User id="26" rating="3"/>
</Users>
</Restaurant>
<Restaurant id="2">
<Users>
<User id="25" rating="2"/>
<User id="26" rating="5"/>
</Users>
</Restaurant>
</Restaurants>
</Similarity>
&#13;
EDIT
根据您的结构,可能有两种方法可能会有所帮助。
public List<Review> RestReviews(Guid rIdThatYouWantoMatch)
{
var reviewsOfOneRestaurant= this.Reviews.Where(r=> r.RestaurantId == rIdThatYouWantoMatch).ToList();
if( reviewsOfOneRestaurant.Count() < 3)
{
return null;
}
else
{
foreach (var review in reviewsOfOneRestaurant)
{
var user= this.Users.Where(u=> u.Id == review.UserId).SingleOrDefault();
if( this.Reviews.Where(r=> r.UserId == user.Id).Count() < 2)
reviewsOfOneRestaurant.Remove(review);
}
return reviewsOfOneRestaurant;
}
}
public List<Match> MatchReview(List<Review> one,List<Review> two)
{
List<Match> list=new List<Match>();
foreach (var review in one)
{
var review2= two.Where(r=>r.UserId == review.UserId).SingleOrDefault();
if( review2 != null)
{
Match match = new Match();
match.rev1=review;
match.rev2=review2;
}
}
return list;
}
对于查询性能,
1)您应该将rateCount添加到用户表
2)考虑以餐厅为基础的评论存储。对于每个餐厅,你应该做一个桌子
您可以在
<Similarity>
<Users>
<User id="1" rateCount="1"/>
<User id="2" rateCount="2"/>
<User id="3" rateCount="3"/>
<User id="4" rateCount="3"/>
<User id="5" rateCount="1"/>
<User id="6" rateCount="1"/>
</Users>
<Restaurants>
<Restaurant id="1" userCount="3">
<User id="2" rating="1"/>
<User id="3" rating="4"/>
<User id="4" rating="2"/>
</Restaurant>
<Restaurant id="2" userCount="5">
<User id="1" rating="2"/>
<User id="2" rating="5"/>
<User id="3" rating="3"/>
<User id="4" rating="1"/>
<User id="5" rating="5"/>
</Restaurant>
<Restaurant id="2" userCount="4">
<User id="2" rating="5"/>
<User id="3" rating="3"/>
<User id="4" rating="2"/>
<User id="6" rating="2"/>
</Restaurant>
</Restaurants>
</Similarity>
&#13;