首页 文章

匹配评论相同餐厅的用户

提问于
浏览
5

我正在研究一种计算餐馆间相似系数的算法 . 在我们能够计算出所述相似度之前,我需要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语句来检查'matches'的数量:

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循环的餐馆数量时需要花费很多时间 .

我认为更好的方法是检索已满足此要求的所有餐馆,但我'm stuck on how to do this. I think you can retrieve a list of '匹配'这将是一个元组列表,如下所示: 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
}

我正在寻找比我目前的方法表现更好的东西,是否有人可以指出我正确的方向或建议更好的方法?另外,如果您需要更多信息,请告诉我们!提前致谢!

Edit: 第一个例子显示了两家餐馆,但当然列表可能更大 . 关键在于我只想要能够计算相似度的餐馆 .

所以请看以下示例:

| 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之间 . 因为没有可能无法计算相似性 . 因此优化这一点的方法是创建一个可以计算相似性的餐馆列表 .

为了进一步解释, match 是两个用户评价两家餐馆的地方 . Restaurant 3 有3条评论,但只有2条评论,因为 User 6 只评了该餐厅 .

因此,如果我们将上面的3家餐厅作为输入,它应该只创建一个可以计算相似性的餐馆列表(在这种情况下只有餐馆1和2) .

Edit 2: 我将添加一个示例,说明我想要的输出应该如何:

“匹配”是至少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个这样的匹配,其中每个对象具有相同的 RestaurantId 来自rev1,而相同的 RestaurantId 来自rev2 .

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

  • 第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上是相同的

我知道ID是guids,但这仅仅是一个例子 .

我希望这是有道理的..

2 回答

  • 3

    我想我已经完成了你想要达到的目标 .

    我've built a database with the Reviews table in your post and I'已经将您在 Edit 中显示的表格设置为相同的数据 .


    Step 1

    因此,我首先将RestaurantId分组,将值作为该餐厅评价用户的所有评论列表 .

    它给了我们这个:

    enter image description here

    Step 2

    排除对评论少于2个餐厅的用户评分少于3的餐馆 .

    它给了我们这个:

    enter image description here

    Step 3

    我们现在有正确的餐馆列表,但我们需要排除不在匹配中的用户评论 . 然后将所有这些都放在餐厅和评论中 .

    它给了我们这个, this is the final result

    enter image description here


    Here is the code :

    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

  • -2

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

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

    <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>
    

    编辑
    根据您的结构,可能低于2种方法可能会有所帮助 .

    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添加到User表
    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>
    

相关问题