简短版本:
我有类似StackOverflow的设置。用户获得成就。我有比SO更多的成就,让我们说10k的顺序,每个用户都有100多个成就。现在,您如何推荐(推荐)用户尝试的下一个成就?
长版:
在django中对象建模如此(仅显示重要部分):
class User(models.Model):
alias = models.ForeignKey(Alias)
class Alias(models.Model):
achievements = models.ManyToManyField('Achievement', through='Achiever')
class Achievement(models.Model):
points = models.IntegerField()
class Achiever(models.Model):
achievement = models.ForeignKey(Achievement)
alias = models.ForeignKey(Alias)
count = models.IntegerField(default=1)
我的算法只是找到与登录用户共享成就的所有其他用户,然后完成所有成就并按出现次数排序:
def recommended(request) :
user = request.user.get_profile()
// The final response
r = {}
// Get all the achievements the user's aliases have received
// in a set so they aren't double counted
achievements = set()
for alias in user.alias_set.select_related('achievements').all() :
achievements.update(alias.achievements.all())
// Find all other aliases that have gotten at least one of the same
// same achievements as the user
otherAliases = set()
for ach in achievements :
otherAliases.update(ach.alias_set.all())
// Find other achievements the other users have gotten in addition to
// the shared ones.
// And count the number of times each achievement appears
for otherAlias in otherAliases :
for otherAch in otherAlias.achievements.all() :
r[otherAch] = r.get(otherAch, 0) + 1
// Remove all the achievements that the user has already gotten
for ach in achievements :
r.pop(ach)
// Sort by number of times the achievements have been received
r = sorted(r.items(), lambda x, y: cmp(x[1], y[1]), reverse=True)
// Put in the template for showing on the screen
template_values = {}
template_values['achievements'] = r
但它需要FOREVER运行,并且总是返回整个列表,这是不需要的。用户只需要获得前几项成就。
所以,我欢迎提出有关其他算法和/或代码改进的建议。我会在系统中为您提供推荐算法的成就:)
答案 0 :(得分:3)
您可以推荐一种方法,可以查看有多少用户已经拥有这些成就,并推荐那些受欢迎的成就。当他们取得了这些成绩后,你会从名单上下来并推荐一些不太受欢迎的名单。然而,这有一种天真的假设,即每个人都想要获得流行的成就。它可能会使流行的成就变得更受欢迎,而不那么受欢迎的成就......嗯......一个安慰是,它不会占用太多资源,而且可能会非常快速地运行。 (只需保留一份成就列表+实现的次数)
另一种方法(试图根据他已经取得的成就来猜测用户可能会追求的成就)是使用一些机器学习算法。我认为k-nearest neighbor algorithm在这里表现得相当不错。选择一个阈值,然后输出高于此阈值的所有内容。现在,我不知道这是否会比你现有的更快,但是每次用户取得新的成就时,你应该只运行一次推荐引擎,将顶部(比方说)存储为五,然后输出它无论何时需要推荐,都要回复用户。
我希望这会有所帮助。 =)
答案 1 :(得分:2)
我建议你将前三个步骤(achievement,otherAliases,count)作为一个单独的SQL语句。就像现在一样,您发出了大量查询并在Python中总结了数千行,这是您应该委派给数据库的任务。例如代码
for otherAlias in otherAliases : #For every single other user
for otherAch in otherAlias.achievements.all() : #execute a query
r[otherAch] = r.get(otherAch, 0) + 1
成千上万的大量查询。
相反,您可以使用SQL来实现此目的,方法是基于Alias id不同且成就ID相同而加入Achiever。然后按成就ID分组并运行计数。
在下面的查询中,表“B”是其他用户的成就,“成就者”是我们的成就。如果任何其他用户共享成就,则对于他们共享的每个成就,它们在“B”中出现一次。然后我们通过alias_id对它们进行分组并计算它们出现的次数,这样你就可以获得一个不错的id,计数表。
非常粗略的代码(这里没有SQL)
SELECT B.Alias_id, COUNT(B.achievement_id)
FROM Achiever, Achiever as B
WHERE Achiever.achievement_id == B.achievement_id
AND Achiever.Alias_id == <insert current user alias here>;
GROUP BY B.Alias_id
如果按照我认为的方式工作,您将获得其他用户别名的表格,以及他们与当前用户共享的成就数量。
接下来你要做的是一个SQL语句,它使用上面的一个作为“内部选择” - 称之为用户。您可以将其与您的成就表和当前用户的成就表联系起来。除了与当前用户类似的前10位用户外,您可能希望忽略所有用户。
我现在没有时间写一个好的查询,但请查看在指定的10个用户和当前用户之间加入achievement_id的数据库的JOIN语句 - 如果没有,则将该ID设置为NULL存在。过滤器仅显示为NULL的行(未实现的成就)。