匹配评论相同餐馆的用户

时间:2016-12-28 10:19:05

标签: c# entity-framework linq

我正在研究一种计算餐馆间相似系数的算法。在我们能够计算出所述相似度之前,我需要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

所以这些匹配的列表可能如下所示:

  • 第1场比赛:rev1.RestaurantId = 1 | rev2.RestaurantId = 2 | UserId = 11此用户ID在rev1和rev2上是相同的
  • 第2场比赛:rev1.RestaurantId = 1 | rev2.RestaurantId = 2 | UserId = 12此用户ID在rev1和rev2上是相同的
  • 第3场比赛:rev1.RestaurantId = 1 | rev2.RestaurantId = 2 | UserId = 13此用户ID在rev1和rev2上是相同的

我知道ids是guids,但这纯粹就是一个例子。

我希望这是有道理的。

2 个答案:

答案 0 :(得分:3)

我认为我已经完成了你想要达到的目标。

我已在您的帖子中建立了一个包含评论表的数据库,并且我将您在修改中显示的表格设置为相同的数据。

第1步

所以,我首先通过RestaurantId将值列为该餐厅评价用户的所有评论列表。

它给了我们这个:

enter image description here

第2步

排除对评论少于2名余额的用户评分少于3次的餐馆。

它给了我们这个:

enter image description here

第3步

我们现在有正确的餐馆列表,但我们需要排除不在比赛中的用户评论。然后把所有这些都放在餐馆和评论中。

它给了我们这个,这是最终结果

enter image description here

以下是代码:

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

使用我的例子的数据,结果如下:

enter image description here

答案 1 :(得分:-2)

我会建议以下; , 1)如果你没有大量的用户和餐馆,重新考虑使用Guid.Make id值int.Query类型的查询更快。

2)您的输入数据只是int。您可以违反数据冗余以加快查询速度。您可以找到我的数据模型。

&#13;
&#13;
<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;
&#13;
&#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)考虑以餐厅为基础的评论存储。对于每个餐厅,你应该做一个桌子 您可以在

下找到建议的数据结构

&#13;
&#13;
<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;
&#13;
&#13;