MapReduce程式設計基礎(二)——數值概要(計算最大值、最小值、平均值)

元氣滿滿的少女程式設計師發表於2017-07-22

數值概要

數值概要模式是計算資料集聚合統計的一般性模式

適用場景:
要處理的資料數值或者計數
資料可以按某些特定的欄位分組

數值概要的應用:
單詞計數
記錄計數
最大/最小值計數
平均值/中位數/標準差

話不多說,現在直接開始我們的第一個示例,最大值、最小值計數示例

最大值、最小值計數示例

1.資料集:

本示例使用Movielens資料集中的u1.base檔案,MovieLens資料集是一個使用者對電影的評分資料集,在後續的示例中我們將一直使用這個資料集,我會將這個資料集上傳到CSDN方便大家下載,檔案的格式如下所示:
第1列到第4列分別代表使用者ID,專案ID,使用者對專案的評分時間戳

1   1   5   874965758
1   2   3   876893171
1   3   4   878542960
1   4   3   876893119
1   5   3   889751712
1   7   4   875071561
1   8   1   875072484
...      ....    ....
943 1067    2   875501756
943 1074    4   888640250
943 1188    3   888640250
943 1228    3   888640275
943 1330    3   888692465

2.程式示例

問題:對於給定的使用者專案評分資料,確定每個使用者評分的最大值、最小值、該使用者的評分次數及該使用者的平均評分。

1)自定義Writable型別儲存輸出資料

注意:實現Writable介面必須實現readFields(DataInput in)和write(DataOutput out)方法。定義toString()方法,才能正確解析輸出。

package mapreduce.design.parrerns;

import java.io.*;

import org.apache.hadoop.io.Writable;

/*
 * 一個定製的Writable物件類,便於定製特定個數的輸入和輸出
 * Writable介面有兩個方法,Write和readFile
 * */
public class MinMaxCountTuple implements Writable{
    private int min=0;//使用者評過的最低分
    private int max=0;//使用者評過的最高分
    private double average=0;//使用者的評分
    private long count=0;//使用者的評分數

    public int getMin(){
        return min;
    }

    public void setMin(int min){
        this.min=min;
    }

    public int getMax(){
        return max;
    }

    public void setMax(int max){
        this.max=max;
    }

    public double getAverage(){
        return average;
    }

    public void setAverage(double average){
        this.average=average;
    }

    public long getCount(){
        return count;
    }

    public void setCount(long count){
        this.count=count;
    }

    @Override   
    public void readFields(DataInput in) throws IOException{
        //返回writable例項的反序列化流,注意:欄位的順序和write()方法相同
        min=in.readInt();
        max=in.readInt();
        count=in.readLong();    
        average=in.readDouble();
    }
    @Override
    public void write(DataOutput out) throws IOException{
        //返回writable例項的反序列化流,注意:欄位的順序和write()方法相同
        out.writeInt(this.min);
        out.writeInt(this.max);
        out.writeLong(this.count);
        out.writeDouble(this.average);
    }

    //定義toString在輸出時才能正確解析
    public String toString(){
        return "min:"+min+" "+"max:"+max+"  "+"count:"+count+"  "+"average:"+average;
    }
}
2)mapper程式碼

輸出鍵是使用者ID,輸出值是最大評分、最小評分及使用者評分數。這三個欄位存貯在MinMaxCountTuple型別的Writable物件中。在map端,我們將最大評分、最小評分都設定成了當前讀入資料中的評分值。計數值為1,表示該使用者評分了一次,最後所有的計數將在reduce端進行彙總。

public static class MinMaxCountMapper extends Mapper<Object, Text, Text, MinMaxCountTuple>{
        private Text outUserId=new Text();//輸出健
        private MinMaxCountTuple outTuple=new MinMaxCountTuple();//輸出值,為自定義型別

        public void map(Object key, Text value, Context context) throws IOException,InterruptedException{
            String[] data=value.toString().split("  ");
            if(data.length==4){
                //輸出的鍵
                outUserId=new Text(data[0]);
                //輸出的值,自定義Writable型別
                outTuple.setMin(Integer.valueOf(data[2]));//最小評分
                outTuple.setMax(Integer.valueOf(data[2]));//最大評分
                outTuple.setAverage(Double.valueOf(data[2]));//平均評分
                outTuple.setCount(1);//評分數目計數
            }
            context.write(outUserId, outTuple);
        }
    }
3)reducer程式碼

reducer遍歷(一個鍵對應的)所有值得到其最小評分、最大評分並計算次數的總和。

public static class MinMaxCountReducer extends Reducer<Text, MinMaxCountTuple, Text,MinMaxCountTuple>{
        private MinMaxCountTuple result=new MinMaxCountTuple();

        public void reduce(Text key, Iterable<MinMaxCountTuple> values, Context context) throws IOException,InterruptedException{
            //初始化
            result.setMin(100);
            result.setMax(0);
            result.setAverage(0.0);
            result.setCount(0);


            double average=0;
            double sumNew=0;
            int countNew=0;

            //迭代迴圈這個鍵值的所有輸入
            for(MinMaxCountTuple val:values){

                if(result.getMax()<val.getMax()){
                    result.setMax(val.getMax());
                }
                if(result.getMin()>val.getMin()){
                    result.setMin(val.getMin());
                }
                //注意sumNew+=平均評分*評分次數
                sumNew+=val.getAverage()*val.getCount();//評分和
                countNew+=val.getCount();//評分次數
            }
            result.setCount(countNew);

            average=sumNew/countNew;
            result.setAverage(average); 

            context.write(key, result);
        }

    }
4)combiner優化

本示例為了直接使用reducer作為combiner進行了特殊處理,本來計算最大評分,最小評分,和統計使用者評分數是滿足交換律和結合律的,意思就是可以任意的改變值的順序,並且隨意的將計算進行分組但是不會該別最終的計算結果,那麼就可以使用combiner。
但是計算平均值是不滿足交換率和結合律的,因為如果每個combiner對本地的map輸出計算平均值,再將各自計算的到的平均值傳送到reduce進行平均值計算,得到的結果會是錯誤的。即,平均值的平均值與真實值的平均值不同。
因此,為了避免以上錯誤,同時利用combiner來優化計算,在計算總評分的時候使得:
評分總和+=平均評分*評分次數
這樣就可滿足交換律和結合律,可以使用combiner對本地map的輸出進行計算。

總結

combiner的使用:只有在map輸出資料的處理滿足交換律和結合律的情況下才能使用combiner。

相關文章