MapReduce样式的练习

在上一期《编程风格练习》中,我们使用Hazelcast库解决了词频问题。 这次,我们将为此使用MapReduce方法。

这是《编程风格练习》重点系列的 19 帖子。其他帖子包括:

  1. 以编程风格介绍练习
  2. 以编程风格进行练习,将内容堆叠起来
  3. 编程风格的练习,Kwisatz Haderach风格
  4. 编程风格的练习,递归
  5. 具有高阶功能的编程风格的练习
  6. 以编程风格进行练习
  7. 以编程风格进行练习,回到面向对象的编程
  8. 编程风格的练习:地图也是对象
  9. 编程风格的练习:事件驱动的编程
  10. 编程风格的练习和事件总线
  11. 反思编程风格的练习
  12. 面向方面的编程风格的练习
  13. 编程风格的练习:FP&I / O
  14. 关系数据库风格的练习
  15. 编程风格的练习:电子表格
  16. 并发编程风格的练习
  17. 编程风格的练习:在线程之间共享数据
  18. 使用Hazelcast以编程风格进行练习
  19. MapReduce风格的练习 (本文)
  20. 编程风格的练习总结

简而言之MapReduce

MapReduce是一个包含两个步骤的过程:

  1. 映射 :执行转换,过滤和分类为不同的“队列”
  2. 减少 :将“队列”的内容汇总到结果中

MapReduce的最大的好处是,既映射减少步骤可以并行的两个潜在的执行。 这非常适合处理大型数据集。

下图有助于可视化整体流程:

MapReduce活动图

虽然MapReduce使并行成为可能,但实现它不是强制性的: 并行只是一个选择 。 原始Python代码和Kotlin端口均未使用它。

迁移到Kotlin

参考代码使用Python yield关键字:函数不会返回简单的项目集合 ,而是 。 尽管代码看起来完全一样,但实际情况却有所不同。 与标准馆藏相比,与流中关联的项目数成比例地提高了性能。 在Java中,使用Stream实现Stream ,而在Kotlin中,使用Sequence

为此,Kotlin提供了Iterable<T>.asSequence()扩展功能。 要更深入地了解Kotlin中的集合和序列以及它们与Java流的关系,请查看此早期文章

应用于眼前的问题,处理管道如下所示:

数据处理管道,从文件名到哈希图

这将转换为以下代码:

funrun(filename:String):Map<String,Int>=read(filename)
  .asSequence()
  .chunked(200)
  .map(::splitWords)
  .reduce{acc,pair->countWords(acc,pair)}
  .sortedBy{it.second}
  .takeLast(25)
  .toMap()

还原概述

map()函数已在本系列的先前文章中得到了充分使用。 此外,我相信大多数开发人员已经以某种方式熟悉它。

另一方面, reduce()函数即使使用了太多,也很少被理解。 例如,在Java中,流的终端操作是归约函数:这些函数包括sum()average()max()以及collect() ! 从collect() Javadoc:

使用Collector在此流的元素上执行可变还原操作。

收集函数只是Stream提供的更通用的reduce()函数的特化:

流类图专注于reduce

如图所示,减少有三种“味道”:

  1. 第一种形式接受单个BinaryOperator参数。 BinaryOperator<T>接受两个T类型的参数,并将它们组合在一起以返回T 请注意,由于起始流可能为空,因此该方法返回Optional<T>

    例如,以下代码段汇总了流中的项目:

    Stream.of(1,2,3,4,5)
          .reduce{a,b->a+b}
  2. 第二种风味类似于第一种,但需要一个起始值。 因此,返回类型不是Optional<T>而是T 如果流为空,则结果将为起始值。 如果不是,它应该与先前的风味相同,但前提是起始值是还原功能的中性元素。
    Stream.of(1,2,3,4,5)
          .reduce{a,b->a+b}
  3. 第三个也是最后一个风味允许以更复杂的代价更改返回的类型

在Kotlin中,还原与Java中的还原非常相似。 但是,有一点区别:接受起始值的函数称为fold() ,不接受的函数称为reduce() 。 另外,它们两个都还提供了另一个签名,该签名提供了当前项目的索引。

签名 起始值 索引

<S, T : S> reduce(op: (S, T) → S): S

<S, T : S> reduceIndexed(op: (Int, S, T) → S): S

<T, R> fold(initial: R, op: (R, T) → R): R

<T, R> foldIndexed(initial: R, op: (Int, R, T) → R): R

以上处理空序列的无起始值函数将在运行时引发异常。 这是我认为Kotlin API与Jav​​a相比显得苍白的少数地方之一。 我对Kotlin的Slack提出了要点,将来的stdlib版本中可能会有一个新的xxxOrNull()。

Kotlin中已实现的功能

映射函数仅从行列表中创建对数为1的单词对:

funsplitWords(lines:Iterable<String>):Iterable<Pair<String,Int>>{
  funIterable<String>.scan()=this
    .flatMap{it.toLowerCase().split("\\W|_".toRegex())}
    .filter{it.isNotBlank()&&it.length>=2}
  funIterable<String>.removeStopWords():Iterable<String>{
    valstopWords=read("stop_words.txt")
      .flatMap{it.split(",")}
    returnthis-stopWords
  }
  returnlines.scan()
    .removeStopWords()
    .map{itto1}
}

我相信这很简单。 还原功能结合了字频率的两个iterables共同创造一个新的迭代。

funcountWords(frequencies1:Iterable<Pair<String,Int>>,
               frequencies2:Iterable<Pair<String,Int>>):Iterable<Pair<String,Int>>{
  valresults=mutableMapOf<String,Int>()
  frequencies1.forEach{
    results.merge(it.first,it.second){
      count,value->count+value
    }
  }
  frequencies2.forEach{
    results.merge(it.first,it.second){
      count,value->count+value
    }
  }
  returnresults.toList()
}

这很粗糙,但是行得通。 可悲的是,我发现没有其他优雅的方法可以实现合并。 提案欢迎!

一路上有点打ic

那可能是终点,但是不幸的是不是。 对于最小的数据集(每个单词的频率为一个,最大的单词的频率),大多数测试都可以成功。 但是,它对于以下示例失败:

White tigers live mostly in India
Wild lions live mostly in Africa

其背后的原因是,映射函数将默认频率分配为1,但读取的行被分成大小为200的单词块(请参见上文)。 由于样本不够大,因此未应用 countWords() 缩减函数 。 因此,单词对/ 1不会合并在一起,并且每个单词的结果映射图的频率为1。

为了返回正确的结果,应该先检查样本大小,然后再通过MapReduce流程将其发送出去。

结论

MapReduce没什么大不了的。 大多数开发人员已经在不知情的情况下使用它。 唯一的挑战是编码正确的映射和归约函数。

另外,应该特别注意输入的大小及其处理方式,因为如果输入的大小太小,将不需要reduce函数,也不会执行其代码。 但是,在大多数情况下,这不应该成为问题,因为MapReduce的目标是大型数据集。

这篇文章的完整源代码可以在Github上找到。

翻译自: https://blog.frankel.ch/exercises-programming-style/19/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java API方式的MapReduce是Hadoop中最常用的方式之一,MapReduce是一种分布式计算模型,可用于对大规模数据进行处理和分析。下面是一个使用Java API方式的MapReduce练习: 假设我们有一个文本文件,其中包含一些单词和它们出现的次数,我们需要编写一个程序来统计每个单词出现的次数。我们可以使用MapReduce来完成这个任务。 1. 创建一个Java项目,并添加Hadoop依赖。 2. 创建一个Java类来实现Mapper接口。在这个类中,我们需要实现map()方法,这个方法将会接收到输入键值对,即每行文本内容和行号。我们需要将文本内容切分成单个单词,并将每个单词作为键,将值设置为1,表示出现次数为1。 ```java public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> { private final static IntWritable one = new IntWritable(1); private Text word = new Text(); public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String line = value.toString(); StringTokenizer tokenizer = new StringTokenizer(line); while (tokenizer.hasMoreTokens()) { word.set(tokenizer.nextToken()); context.write(word, one); } } } ``` 3. 创建一个Java类来实现Reducer接口。在这个类中,我们需要实现reduce()方法,这个方法将会接收到键值对列表,每个键值对中的键为单词,值为出现次数。我们需要将每个单词的出现次数加起来,并将结果作为值输出。 ```java public class WordCountReducer extends Reducer<Text, IntWritable, Text, IntWritable> { private IntWritable result = new IntWritable(); public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { int sum = 0; for (IntWritable val : values) { sum += val.get(); } result.set(sum); context.write(key, result); } } ``` 4. 在主方法中,我们需要设置Job信息,并将Mapper和Reducer类设置为Job的输入和输出。还需要设置输入文件和输出文件的路径信息。 ```java public static void main(String[] args) throws Exception { Configuration conf = new Configuration(); Job job = Job.getInstance(conf, "word count"); job.setJarByClass(WordCount.class); job.setMapperClass(WordCountMapper.class); job.setCombinerClass(WordCountReducer.class); job.setReducerClass(WordCountReducer.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); FileInputFormat.addInputPath(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); System.exit(job.waitForCompletion(true) ? 0 : 1); } ``` 5. 在命令行中运行程序,并指定输入文件和输出文件的路径信息。 ```bash hadoop jar WordCount.jar /input /output ``` 以上就是使用Java API方式的MapReduce练习,通过这个练习,我们可以更深入地了解MapReduce的实现原理和使用方法,同时也能更好地掌握Hadoop的使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值