需求分析:
通过对购买组合的分析可以对商品进行捆绑销售或者特殊促销的手段来提高销量,利用Map统计所有用户的购买商品情况,在Reduce阶段将所有商品组合,最后在cleanup阶段统计各种组合商品的购买次数并输出Top5。
测试数据如下:
结果:
可视化:如下图4所示
部分代码分析:
reduce阶段:
map阶段use_ID和time为key,ite_ID为value。reduce阶段购买组合为key,组合购买次数为value。
reduce阶段取出map的value:同一用户同一时间购买的商品,并放在一个集合中,嵌套的for循环是在集合中分别组合同一用户同一时间购买的商品,并将所有组合商品放到一个新的集合中。
注:要用嵌套for循环将ite_ID两两组合,需要将item放到集合中!
这段代码的作用是为了避免下述情况:
这两个组合实际上是相同的。所以,将ite_ID的第一个字符排序避免上述情况的发生。
cleanup阶段:
将集合中的各种ite_ID组合相比较,相等则sum++,来统计各种组合的个数。
list2.remove这行代码的作用是为了避免下述情况的发生:
集合中前面的元素已完成遍历整个集合的比较并计数,如果不删除集合后面的元素,再比较前面的元素,会重复计数。
注:此循环不能写在reduce中,只能写在clean up阶段。因为reduce()会执行多次,一个key就会执行一次reduce()。而对于组合的统计要在所有的ite_ID组合写到集合中后计数,所以要写在cleanup中。
完整代码:
map阶段:
package combination;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
public class CombinationMapper extends Mapper<LongWritable,Text,Text,Text>{
Text k=new Text();
Text v=new Text();
@Override
protected void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
//1 获取一行
String line = value.toString();
//2 切割单词
String[] fields = line.split("\t");
String keywords=fields[0]+" "+fields[5];
//3 use_ID和time为key,ite_ID为value
k.set(keywords);
v.set(fields[2]);
//4 写出(测试数据假定0为购买)
if(fields[4].equals("0")){
context.write(k, v);
}
}
}
reduce阶段:
package combination;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
public class CombinationReducer extends Reducer<Text,Text,Text,IntWritable>{
Map<String,Integer> map=new HashMap<String,Integer>();
IntWritable v=new IntWritable();
List<Text> list2=new ArrayList<>();
@Override
protected void reduce(Text key, Iterable<Text> values, Context context)
throws IOException, InterruptedException {
String combination;
List<Text> list1=new ArrayList<>();
for (Text value : values) {
Text e=new Text(value.toString());
list1.add(e);
}
for(int i=0;i<list1.size();i++){
for(int j=i+1;j<list1.size();j++){
if(list1.get(i)!=list1.get(j)){
if(list1.get(i).charAt(0)<list1.get(j).charAt(0)){
combination=list1.get(i)+" "+list1.get(j);
}else{
combination=list1.get(j)+" "+list1.get(i);
}
Text t=new Text(combination);
list2.add(t);
// context.write(new Text(combination), new IntWritable(1));
}
}
}
}
//
@Override
protected void cleanup(Context context) throws IOException, InterruptedException {
for(int m=0;m<list2.size();m++){
int sum=1;
for(int n=m+1;n<list2.size();n++){
if(list2.get(m).equals(list2.get(n))){
sum++;
list2.remove(n);
// context.write(list2.get(m),new IntWritable(sum));
}
}
// context.write(list2.get(m),new IntWritable(sum));
map.put(list2.get(m).toString(), sum);
}
//这里将map.entrySet()转换成list
List<Map.Entry<String,Integer>> list=new LinkedList<Map.Entry<String,Integer>>(map.entrySet());
//通过比较器来实现排序
Collections.sort(list, new Comparator<Map.Entry<String, Integer>>(){
//升序排序
@Override
public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
return (int)(o2.getValue()-o1.getValue());
}
});
for(int i=0;i<3;i++){
context.write(new Text(list.get(i).getKey()), new IntWritable(list.get(i).getValue()));
}
}
}
driver:
package combination;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class Combination {
public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException{
Configuration conf = new Configuration();
//1 获取job对象
Job job = Job.getInstance(conf);
//2 设置jar存储位置
job.setJarByClass(Combination.class);
//3 关联map和reduce类
job.setMapperClass(CombinationMapper.class);
job.setReducerClass(CombinationReducer.class);
//4 设置Mapper阶段输出数据的key和value类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(Text.class);
//5 设置最终数据输出的key和value类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
//6 设置输入路径和输出路径
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
//7 提交job
//job.submit();
boolean result = job.waitForCompletion(true);
System.exit(result? 0 : 1);
}
}