【大数据分析常用算法】6.共同好友

简介

给定一个包含上千万用户的社交网络,我们会实现一个MapReduce、Spark程序,在所有用户对中找出“共同好友”。

令 $$ {U_1,U_2,...,U_n} $$ 为包含一个所有用户列表的集合。我们的目标是为每个$(U_i,U_j)$对$i\ne j$找出共同好友。

我们本章提出3个解决方案:

  • MapReduce/Hadoop解决方案,使用基本数据类型

  • Spark解决方案,使用弹性数据集RDD。

1、共同好友的概念

如今大多数社交网络网站都提供了有关的服务,可以帮助我们与好友共享信息、图片和视频。

有些网站树森之还提供了视频聊天服务,帮助你与好友保持联系。根据定义,“好友”是指你认识、喜欢和信任的一个人。

比如我们QQ好友列表,这个列表上好友关系都是双向的。如果我是你的好友,那么你也是我的好友,注意这个特点,我们的程序中运用了这个特点,将这种关系进行合并求解交集,即可得到A,B的共同好友。

有很多办法可以找到共同好友:

  • 使用缓存策略,将共同好友保存在一个缓存中(redis、memcached)
  • 使用Mapreduce离线计算,每隔一段时间(例如一天)计算一次每个人的共同好友并存储这些结果;

1、POJO共同好友解决方案

令${A_1,A_2,...A_n}$是$user_1$的好友集合,${B_1,B_2,...,B_n}$是$user_2$的好友集合,那么,$user_1,user_2$的共同好友集合就可以定义为 $$ A \cap B $$ 即两个集合的交集。

POJO的简单实现如下所示:

package com.sunrun.movieshow.algorithm.friend;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class POJOFriend {
    public static Set<String> intersection(Set<String> A, Set<String> B){
        if(A == null || B == null){
            return null;
        }

        if(A.isEmpty() || B. isEmpty()){
            return null;
        }

        Set<String> result = new HashSet<>();
        result.addAll(A);
        result.retainAll(B);
        return result;
    }

    public static void main(String[] args) {
        Set<String> A = new HashSet<>();
        Set<String> B = new HashSet<>();

        A.add("A");
        A.add("B");
        A.add("C");

        B.add("B");
        B.add("C");
        B.add("D");

        System.out.println(intersection(A,B));
        /**
         * [B, C]
         */

    }
}

2、MapReduce解决方案

映射器接受一个$(k_1,v_1)$,其中$k_1$是一个用户,$v_1$是这个用户的好友列表。

映射器发出一组新的$(k_2,v_2)$,$k_2$是一个$Tuple2(k1,f_i)$,其中$f_i \in v_1$,即会迭代所有的好友列表和$k_1$进行两两组合。

归约器的key是一个用户对,value则是一个好友集合列表。reduce函数得到所有好友集合的交集,从而找出$(u_i,u_j)$对的共同好友。

至于在Mapper过程中的数据传输,会关联到数组类型,我们有两个方案:

1、依然使用文本形式,在Driver节点进行解析;

2、如果你的信息需要解析为非String,例如Long等,可以使用ArrayListOfLongsWritable类。

他是一个实现了Hadoop串行化协议的类,我们不必自己去实现这些接口,可以直接从第三方组件里面抽取使用

<dependency>
    <groupId>edu.umd</groupId>
    <artifactId>cloud9</artifactId>
    <version>1.3.2</version>
</dependency>

该组件包含了丰富的实现了hadoop序列化的协议提供使用,比如PairOfStrings以及ArrayListOfLongWritable等。

2.1、Mapper

package com.sunrun.movieshow.algorithm.friend.mapreduce;

import edu.umd.cloud9.io.pair.PairOfStrings;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.Writable;
import org.apache.hadoop.mapreduce.Mapper;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class FriendMapper extends Mapper<LongWritable, Text, Text, Text> {

    private static final Text KEY = new Text();
    private static final Text VALUE = new Text();

    // 获取朋友列表
    static String getFriends(String[] tokens) {
        // 不可能再有共同好友
        if (tokens.length == 2) {
            return "";
        }
        StringBuilder builder = new StringBuilder();
        for (int i = 1; i < tokens.length; i++) {
            builder.append(tokens[i]);
            if (i < (tokens.length - 1)) {
                builder.append(",");
            }
        }
        return builder.toString();
    }


    // 使key有序,这里的有序只的是key的两个用户id有序,和整体数据无关
    static String buildSortedKey(String user, String friend) {
        long p = Long.parseLong(user);
        long f = Long.parseLong(friend);
        if (p < f) {
            return user + "," + friend;
        } else {
            return friend + "," + user;
        }
    }

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        String[] tokes = value.toString().split(" ");

        // user
        String user = tokes[0];

        // value
        VALUE.set(getFriends(tokes));

        // rescue keys
        for (int i = 1; i < tokes.length ; i++) {
            String otherU = tokes[i];
            KEY.set(buildSortedKey(user,otherU));
            context.write(KEY,VALUE);
        }
    }
}

2.2、Reducer

package com.sunrun.movieshow.algorithm.friend.mapreduce;

import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;
import java.util.*;

public class FriendReducer extends Reducer<Text, Text,Text, Text> {

    static void addFriends(Map<String, Integer> map, String friendsList) {
        String[] friends = StringUtils.split(friendsList, ",");
        for (String friend : friends) {
            Integer count = map.get(friend);
            if (count == null) {
                map.put(friend, 1);
            } else {
                map.put(friend, ++count);
            }
        }
    }

    @Override
    protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
        Map<String, Integer> map = new HashMap<String, Integer>();
        Iterator<Text> iterator = values.iterator();
        int numOfValues = 0;
        while (iterator.hasNext()) {
            String friends = iterator.next().toString();
            if (friends.equals("")) {
                context.write(key, new Text("[]"));
                return;
            }
            addFriends(map, friends);
            numOfValues++;
        }

        // now iterate the map to see how many have numOfValues
        List<String> commonFriends = new ArrayList<String>();
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            //System.out.println(entry.getKey() + "/" + entry.getValue());
            if (entry.getValue() == numOfValues) {
                commonFriends.add(entry.getKey());
            }
        }

        // sen it to output
        context.write(key, new Text(commonFriends.toString()));
    }
}

3、Spark解决方案

package com.sunrun.movieshow.algorithm.friend.spark;

import avro.shaded.com.google.common.collect.ImmutableCollection;
import com.google.common.collect.Sets;
import com.sunrun.movieshow.algorithm.common.SparkHelper;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import scala.Tuple2;
import shapeless.Tuple;

import java.util.*;

/**
 * 共同好友的Spark解决方案
 * 输入:
 * [root@h24 ~]# hadoop fs -cat /friend/input/*
 * A B C D E
 * B A C D
 * C A B D E
 * D A B C
 * E A C
 * F A
 * 即第一列代表当前用户,后面的数据代表其好友列表。
 *
 * 输出
 * (A,B) => C,D
 *  两个好友之间的公共好友。
 */
public class FriendSpark {
    // 构建有序key,避免重复
    static Tuple2<String,String> buildSortedKey(String u1, String u2){
        if(u1.compareTo(u2) < 0){
            return new Tuple2<>(u1,u2);
        }else{
            return new Tuple2<>(u2,u1);
        }
    }

    public static void main(String[] args) {
        // 1.读取配置文件
        String hdfsUrl = "hdfs://10.21.1.24:9000/friend/";
        JavaSparkContext sc = SparkHelper.getSparkContext("CommonFriend");
        JavaRDD<String> rdd = sc.textFile(hdfsUrl + "input");

        // 2.解析内容
        /**
         * A B C
         * ((A,B),(B,C))
         * ((A,C),(B,C))
         */
        JavaPairRDD<Tuple2<String, String>, List<String>> pairs = rdd.flatMapToPair(line -> {
            String[] tokes = line.split(" ");

            // 当前处理的用户
            String user = tokes[0];

            // 该用户的好友列表
            List<String> friends = new ArrayList<>();
            for (int i = 1; i < tokes.length; i++) {
                friends.add(tokes[i]);
            }

            List<Tuple2<Tuple2<String, String>, List<String>>> result = new ArrayList<>();
            // 算法处理,注意顺序,依次抽取每一个好友和当前用户配对作为key,好友列表作为value输出,
            // 但如果该用户只有一个好友的话,那么他们的共同好友应该设置为空集
            if (friends.size() == 1) {
                result.add(new Tuple2<>(buildSortedKey(user, friends.get(0)), new ArrayList<>()));
            } else {
                for (String friend : friends) {
                    Tuple2<String, String> K = buildSortedKey(user, friend);
                    result.add(new Tuple2<>(K, friends));
                }
            }

            return result.iterator();
        });

        /**
         *  pairs.saveAsTextFile(hdfsUrl + "output1");
         * ((A,B),[B, C, D, E])
         * ((A,C),[B, C, D, E])
         * ((A,D),[B, C, D, E])
         * ((A,E),[B, C, D, E])
         * ((A,B),[A, C, D])
         * ((B,C),[A, C, D])
         * ((B,D),[A, C, D])
         * ((A,C),[A, B, D, E])
         * ((B,C),[A, B, D, E])
         * ((C,D),[A, B, D, E])
         * ((C,E),[A, B, D, E])
         * ((A,D),[A, B, C])
         * ((B,D),[A, B, C])
         * ((C,D),[A, B, C])
         * ((A,E),[A, C])
         * ((C,E),[A, C])
         * ((A,F),[])
         */

        // 3.直接计算共同好友,步骤是group以及reduce的合并过程。
        JavaPairRDD<Tuple2<String, String>, List<String>> commonFriends = pairs.reduceByKey((a, b) -> {
            List<String> intersection = new ArrayList<>();
            for (String item : b) {
                if (a.contains(item)) {
                    intersection.add(item);
                }
            }
            return intersection;
        });

        commonFriends.saveAsTextFile(hdfsUrl + "commonFriend");
        /**
         * [root@h24 ~]# hadoop fs -cat /friend/commonFriend/p*
         * ((A,E),[C])
         * ((C,D),[A, B])
         * ((A,D),[B, C])
         * ((C,E),[A])
         * ((A,F),[])
         * ((A,B),[C, D])
         * ((B,C),[A, D])
         * ((A,C),[B, D, E])
         * ((B,D),[A, C])
         */
    }
}

转载于:https://my.oschina.net/u/3091870/blog/3025587

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值