【问题标题】:Efficient way to to find Objects with common attributes in 2 python-lists在 2 个 python 列表中查找具有共同属性的对象的有效方法
【发布时间】:2016-06-25 20:43:55
【问题描述】:

标题可能令人困惑 - 我将尝试解释我想要做什么: 我正在学习计算机科学并尝试将一个小型电影推荐器作为我的讲座“数据仓库和数据挖掘”的项目。现在我正在尝试根据他们的电影评分计算 2 个用户的相似度。

class Rating(Model): 
    def __init__(self, userID, movieID, rating):
          ...

我覆盖了 Rating 的 __eq__, __ne__ and __hash__,但只考虑了 movieID,以便可以创建一组 2 个用户的 Ratings 来查找他们都已评分的电影。

def similarity(userA, userB):
    ratingsA = userA.ratings
    ratingsB = userB.ratings
    common_ratings = set((ratingsA, ratingsB))

我现在想要的是如下内容: 2 个列表按相同顺序排序,以便计算用户/他们的评分的余弦距离。

[Rating(userID=1, movieID=4, rating=4.7), Rating(user=1, movie=7, rating=9.8)]
[Rating(userID=2, movieID=4, rating=2.0), Rating(user=2, movie=7, rating=6.6)]    

我真的不喜欢我的方法,但在过去的几个小时里我找不到更好的方法。

另一种效率较低的方法(我认为?)是这样的:

lA = []
lB = []
for rA in ratingsA:
    for rB in ratingsB: 
        if rA.movieID == rB.movieID:
            lA.append(rA)
            lB.append(rB)
sim = cos_dist(lA, lB)

这种方法可能会奏效,但我想运行时间会很糟糕,因为大约有 40000 部电影,而 2 个用户评价相同电影的概率非常低......

也许有人有一种有效的方法? 提前谢谢!

【问题讨论】:

  • 用户能否多次评价同一部电影?如果不是,您应该在append 值之后立即break,因为所有后续迭代都是无用的。
  • Rating 是 SQL 模型吗?因为在这种情况下,最好只使用 SQL 语句来加入表......

标签: python performance set similarity


【解决方案1】:

您的方法是 O(N^2) 最坏的情况。您可以将排序列表的复杂度降低到 O(N log N):

sorted_ratingsA = sorted(ratingsA, lambda x: x.movieID)
sorted_ratingsB = sorted(ratingsB, lambda x: x.movieID)

现在我们可以从最后一个列表中弹出这些项目(出于效率原因),并使用movieID 上的顺序来检查某个ID 是否被用户评分。大致如下:

lA = []
lB = []
maxA = sorted_ratingsA.pop()
maxB = sorted_ratingsB.pop()
while sorted_ratingsA and sorted_ratingsB:
    if maxA.movieID == maxB.movieID:
        lA.append(maxA)
        lb.append(maxB)
        # instead of the following two pop calls you could simply
        # change the elif into a new if statement.
        maxA = sorted_ratingsA.pop()
        maxB = sorted_ratingsB.pop()
    elif maxA < maxB:
        maxB = sorted_ratingsB.pop()
    else:
        maxA = sorted_ratingsA.pop()

如您所见,弹出包含最大值的列表,直到找到相等的 id 或直到 id 低于,在这种情况下,您开始从另一个列表中弹出。列表按升序排列的事实意味着您可以在 O(N log N) 中找到所有匹配项。

使用pop() 是必不可少的,因为弹出list 的结尾需要摊销 O(1) 时间,而使用pop(0) 之类的东西会花费 O(N) pop 平均会重新引入 O(N^2) 因子。


另一种方法是简单地使用散列,这应该可以让您平均花费 O(N)。您首先创建两个从movieID 到评级的映射,然后将这些映射相交:

mapA = {x.movieId: x for x in ratingsA}
mapB = {x.movieId: x for x in ratingsB}
common_keys = mapA.keys() & mapB.keys()

lA = [mapA[k] for k in common_keys]
lB = [mapB[k] for k in common_keys]

如果您使用的是 pythonkeys() 替换为 viewkeys()

注意:即使此解决方案使用散列,lAlB 的顺序也会匹配,因为只有在修改集合时,set 上的迭代顺序才会改变,因此上面的两次迭代会检索相应的评级。但是评分的顺序本身并没有定义(所以你不知道movieID 出现的顺序,但你知道它会在lAlB 之间匹配)。


您没有在问题中提到 SQL,无论如何,如果这些对象位于 SQL 数据库中,最好让数据库为您进行搜索。 您可能有一个包含各种字段的 rankings 表,并且您想要这样做:

SELECT * FROM rankings
JOIN rankings AS rankings2
ON rankings.movieID = rankings2.movieID

【讨论】:

  • 我真的很喜欢你的第一种方法,尽管第二种方法更直接。你能解释一下为什么pop(0)pop() 贵吗?因为它们都弹出第一个元素,所以不应该是一样的吗?非常感谢您的精彩回答!
  • @F.Junkert pop() 弹出 last 元素,而 pop(0) 弹出第一个元素:[1,2,3,4,5].pop() == 5。 Python lists 是“可调整大小的数组”。如果您从开头删除一个项目,则数组必须移动所有其他元素以填充插槽,而如果它位于末尾,则不必担心这样做,并且只会偶尔缩小数组以确保它不会不要浪费太多内存。
  • 我对两者都进行了测试并注意到,您必须先将 mapA.keys() 和 mapB.keys() 转换为集合,然后才能与它们相交。以防万一有人遇到类似问题:common_keys = set(mapA.keys()) &amp; set(mapB.keys())
  • @F.Junkert 在我写的答案中:如果您使用的是python版本keys()替换为viewkeys()common_keys = mapA.viewkeys() &amp; mapB.viewkeys()
  • 我仔细检查过,我正在使用Python 2.7.6
猜你喜欢
  • 1970-01-01
  • 2012-09-24
  • 1970-01-01
  • 1970-01-01
  • 2020-06-09
  • 1970-01-01
  • 2021-11-30
  • 2015-07-17
  • 2013-02-13
相关资源
最近更新 更多