一个计算好友类似度的MapReduce实现(Python版)

     近期把以前协同过滤的练习拿出来炒了回冷饭,结合最近正学着Python,完成了一个计算好友类似度的简单实例。 python

     背景是一个8万多人的小型社区,平均每一个用户添加了4.792名好友,好友数最多的用户有3000多名好友,也有4万多用户没有添加任何好友(挺符合社交网络长尾效应的)。 linux

     话说经典的计算好友交集大小的代码应该是这样的: 算法

for i in user_ids:
    for j in user_ids:
        if i == j:
            continue
        s = len(friends_i & friends_j)

     (friends_i和friends_j分别表示i和j的好友集合) 网络

     这种实现的算法复杂度是O(N^2*M*2),其中N表示用户规模,M表示计算共同好友的两名用户添加的最小好友数。经测算,大概每名用户须要5s的计算时间。 app

     而MapReduce就是把原来一步能完成的工做切成了三步,mapper -> sort -> reducer。其中mapper负责化整为零,把要计算的数据转化成一行一行的key-value,sort负责把相同的key比邻排列,reducer则负责和mapper相反的工做,把零散的value值以必定的模式(好比累加)结合起来。 设计

     网上流传的演示MapReduce的都是一个WordCount的例子,好友类似度的计算涉及到两个对象的关系,数据的输入和key-value的设计仍是要花点心思的。 code

     具体mapper的程序是这样实现的: 对象

def do_map():
     for i in user_ids:
          if friends_i is None:
               continue
          for j in friends_i:
               if  friends_j is None:
                    continue
               for k in friends_j:
                    # 排除k和i原本就已是好友的状况
                    if k == i or k in friends_i:
                         continue
                    print '%s_%s,%s' % (i, k, 1)

     这一步输出的是这样的key-value格式: 排序

74722_10622,1 it

50041_10622,1

74722_10622,1

10622_50041,1

……

     两个有共同好友的id用下划线分割,value表示出现的次数。

     通过了sort以后,相同的key被放在一块儿,变成了这样的排列:

10622_50041,1

50041_10622,1

74722_10622,1

74722_10622,1

……

     reducer是一个标准的累加计数程序,和WordCount的实现并没有二致:

def do_reduce(fin = sys.stdin):
    prev_key = None
    key = None
    current_count = 0
    for line in fin:
        key, count = line.split(',', 1)
        count = int(count)
        if key == prev_key:
            current_count += count
        else:
            if prev_key:
                print >> x, '%s,%s' % (prev_key, current_count)
            current_count = count
            prev_key = key
     #打印最后一行
    if key == prev_key:
        print '%s,%s' % (prev_key, current_count)

     通过reducer以后,相同的key出现的次数被累加,得出任意两个用户的共同好友数,也就是好友类似度的分子:

10622_50041,1

50041_10622,1

74722_10622,2

……

     总共耗时17分21秒。与头一种算法相比,MapReduce的计算时间只有前者的百分之一。

     大概估算了算法复杂度:

     1. maper的算法复杂度大约是O(N*M^2),其中M表示整个社区全部用户的平均好友数,应该能够轻松得证,任意两名用户的好友数积的平均值小于平均好友数的平方。

     2. linux的sort采用的是归并排序,算法复杂度不超过O(N*logN)。

     3. reducer的算法复杂度大约是O(N*M)。

     可见MapReduce确实下降了算法复杂度,另外一方面也归功于Python在文本处理方面的效率。

后记:

     以后好奇了一把,用python的dict重复了实验,发现写dict写到一半就假死了,dict大小是40665020。

相关文章
相关标签/搜索