不等key的reduce

  1. 场景描述
           假设有这样的场景,现在需要计算文章标题的相似度,具体算法见http://www.lanceyan.com/tech/arch/simhash_hamming_distance_similarity.html。接下去算下的结果为:
    标题 相似度值
    A 13
    B 14
    C 15
    D 16
    ...... ......
        现在需要进行去重,去重的规则为:相似度相减的绝对值在3以内(包括3)的认为是同样的标题,我们可以将他们归并为一组。代码的input为上表,output如下显示:
    {'A_13': ['B_14', 'D_16', 'C_15', 'E_16']}
    {'B_14': ['F_17', 'D_16', 'C_15', 'A_13', 'E_16']}
    {'C_15': ['G_18', 'B_14', 'D_16', 'A_13', 'E_16', 'F_17']}
    {'E_16': ['G_18', 'B_14', 'A_13', 'F_17', 'C_15', 'H_19']}
    {'D_16': ['G_18', 'B_14', 'A_13', 'F_17', 'C_15', 'H_19']}
    {'F_17': ['G_18', 'B_14', 'D_16', 'I_20', 'E_16', 'C_15', 'H_19']}
    {'G_18': ['E_16', 'J_21', 'D_16', 'I_20', 'F_17', 'C_15', 'H_19']}
    {'H_19': ['G_18', 'J_21', 'K_22', 'D_16', 'I_20', 'E_16', 'F_17']}
    {'I_20': ['L_23', 'G_18', 'J_21', 'K_22', 'F_17', 'H_19']}
    {'J_21': ['L_23', 'G_18', 'M_24', 'K_22', 'I_20', 'H_19']}
    {'K_22': ['N_25', 'L_23', 'M_24', 'J_21', 'I_20', 'H_19']}
    {'L_23': ['N_25', 'M_24', 'J_21', 'O_26', 'K_22', 'I_20']}
    {'M_24': ['J_21', 'K_22', 'N_25', 'O_26', 'L_23']}
    {'N_25': ['K_22', 'L_23', 'M_24', 'O_26']}
    {'O_26': ['L_23', 'M_24', 'N_25']}
  2. 解决方案
           由于input数据量比较大,只能考虑使用分布式的计算平台计算,选用mapreduce,但是mapreduce从map到reduce端是将key相同的组织到一个list中,并不符合我们的需求,但是我们可以在map的时候将每条输入的数据进行扩维,一条变7条,其中hash值为当条数据的hash值分别加上-3,-2,-1,0,1,2,3。这样设计可以保证绝对值相差为3以内的可以作为key相同的组织到同一个list中,这样会引入重复数据,需要在reduce做相应的处理。算法还算比较简单,直接贴上streaming的mr代码。
        map:
         #!/usr/bin/env python
         # vim: set fileencoding=utf-8
         import sys

         def main(separator = '\t'):
                for data in sys.stdin:
                    _title, _hash = data.strip().split(separator)
                    for i in range(-3, 4):
                        if (i == 0):
                            print '%d\t%s' % (int(_hash) + i, '0_' + _title + '_' + _hash)
                        else:
                            print '%d\t%s' % (int(_hash) + i, '1_' + _title + '_' + _hash)
         if __name__ == '__main__':
            main()
        reduce:
         #!/usr/bin/env python
         # vim: set fileencoding=utf-8
         import sys
         from itertools import groupby
         from operator import itemgetter
         import math

         def read_from_mapper(file, separator):
            for line in file:
                yield line.strip().split(separator, 2)

         def main(separator = '\t'):
            data = read_from_mapper(sys.stdin, separator)
            for key, group in groupby(data, itemgetter(0)):
            try:
                _left = []
                _right = []
                for k, value in group:
                    if (value.startswith('0')):
                        _left.append(value)
                    else:
                        _right.append(value)

                left_rs = list(set(_left))
                right_rs = list(set(_right))

                if (len(left_rs) > 0):
                    for l in left_rs:
                        rs = {}
                        _flag, _title, _hash = l.strip().split('_')
                        titles = []
                        for r in right_rs:
                            _flag_, _title_, _hash_ = r.strip().split('_')
                            titles.append(_title_ + '_' + _hash_)
                            rs[_title + '_' + _hash] = titles
                        print rs

            except ValueError:
                pass
         if __name__ == '__main__':
            main()
        代码比较简单,不做过多的解释,主要是一个扩维的想法。绝对值相差为3这样需要扩充7倍的数据,如果需求改成相差100是否需要扩充201倍的数据,如果本身数据量就很大,那这么做无疑是加大了reduce的难度,有可能跑不出结果。我们需要换一种方式来实现。
  3. 问题解决
           上一节的解决方案并非是最优的解决方法,对于小数据量的扩充是可以的,但是一旦需要扩充的维度太大就无法解决。那么从mapreduce实现的核心去思考是否有别的解决方案,其实我们可以在map的输出key做文章,自定义map的输出key,然后覆写compareTo方法时abs(a - b) <= 3 return 0就可以了。直接贴上自定义map key的代码:
    package xxx.xxx.xx.xx.model.output.map;
    
    import org.apache.hadoop.io.WritableComparable;
    
    import java.io.DataInput;
    import java.io.DataOutput;
    import java.io.IOException;
    
    public class DistinctMapKey implements WritableComparable<DistinctMapKey> {
        private int hashV; // 表示计算出来的相似度值
    
    
        @Override
        public int compareTo(DistinctMapKey o) {
            if (o == null) {
                return 1;
            }
            if (this == o) {
                return 0;
            }
            int result = this.hashV - o.hashV;
            if (result > 3 || result < -3) {
                return result;
            }
            return 0;
        }
    
        @Override
        public void write(DataOutput out) throws IOException {
            out.writeInt(this.hashV);
        }
    
        @Override
        public void readFields(DataInput in) throws IOException {
            this.hashV = in.readInt();
        }
    
        public int getHashV() {
            return hashV;
        }
    
        public void setHashV(int hashV) {
            this.hashV = hashV;
        }
    }
    
    这样就不需要扩充维度,只需要修改compareTo方法。
  4. 更新
    2015年12月15日21点03分更新
    用spark实现第二节中的算法,代码如下:
    package com.xxx.xxx.spark.learning.help
    
    import org.apache.spark.{SparkContext, SparkConf}
    
    import scala.collection.mutable.{ArrayBuffer, HashSet, HashMap}
    
    /**
     * Created by xxx on 12/14/15.
     */
    object RangeKeyReduce {
      def main(args: Array[String]) {
        val conf = new SparkConf()
          .setAppName("hebei")
          .set("spark.executor.memory", "4g")
        val sc = new SparkContext(conf)
    
        val data = sc.textFile("/user/xxx/xxx/range_key/input/data")
    
        val groups = data.flatMap { line =>
          val detail = line.split("\t")
          val title = detail(0)
          val hash = detail(1)
          for (i <- -3 to 3) yield {
            val flag = if (i == 0) 0 else 1
            (hash.toLong + i, flag + "_" + title + "_" + hash)
          }
        }.groupByKey()
    
        val res = groups.map { line =>
          val left: HashSet[String] = new HashSet[String]()
          val right: HashSet[String] = new HashSet[String]()
    
          line._2.foreach { value =>
            if (value.startsWith("0")) left += value
            else right += value
          }
          val rs: HashMap[String, ArrayBuffer[String]] = new HashMap[String, ArrayBuffer[String]]()
          if (left.size > 0) {
            left.foreach { l =>
              val Array(flag, title, hash) = l.split("_")
              val titles: ArrayBuffer[String] = new ArrayBuffer[String]()
              right.foreach { r =>
                val Array(_flag, _title, _hash) = r.split("_")
                titles += (_title + "_" + _hash)
              }
              rs.put(title + "_" + hash, titles)
            }
            rs
          } else {
            new HashMap[String, ArrayBuffer[String]]()
          }
        }.filter(_.size > 0).flatMap { l =>
          for (k <- l.keys) yield {
            k + "\t" + l.get(k).toArray.mkString(";")
          }
        }
    
        res.saveAsTextFile("/user/xxx/xxx/range_key/output")
      }
    }
    


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章