使用MapReduce查找数字的平均值

时间:2012-05-19 17:52:31

标签: java hadoop mapreduce distributed

我一直在尝试编写一些代码来使用MapReduce查找数字的平均值。

我正在尝试使用全局计数器来达到我的目标,但我无法在Mapper的map方法中设置计数器值,而且我也无法在{{{{}}中检索计数器值1}}我的Reducer的方法。

我是否必须在reduce中使用全局计数器(例如,使用提供的map的{​​{1}})?或者你会建议任何不同的逻辑来得到一些数字的平均值吗?

4 个答案:

答案 0 :(得分:7)

逻辑非常简单: 如果所有数字都具有相同的键,则映射器会发送您想要查找具有相同键的平均值的所有值。因此,在reducer中可以对迭代器中的值求和。然后,您可以在迭代器工作的数字时间内保持计数器,这解决了要平均的项目数的问题。最后,在迭代器之后,您可以通过将总和除以项目数来找到平均值。

小心,如果将combiner类设置为与reducer相同的类,则此逻辑将不起作用 ...

答案 1 :(得分:1)

使用所有3 Mapper / Combiner / Reducer解决问题。 请参阅以下链接以获取完整代码&解释

http://alchemistviews.blogspot.com/2013/08/calculate-average-in-map-reduce-using.html

答案 2 :(得分:1)

平均值是总和/大小。如果sum是sum = k1 + k2 + k3 + ...,则可以在求和之后或求和期间除以大小。所以平均值也是k1 /尺寸+ k2 /尺寸+ k3 /尺寸+ ...

Java 8代码很简单:

    public double average(List<Valuable> list) {
      final int size = list.size();
      return list
            .stream()
            .mapToDouble(element->element.someValue())
            .reduce(0,(sum,x)->sum+x/size);
    }

因此,您首先将列表中元素的每个值映射为double,然后通过reduce函数求和。

答案 3 :(得分:0)

算术平均值是一个聚合函数,它不是分布式的而是代数的。根据{{​​3}},如果符合以下情况,则聚合函数是分布式的:

  

[...]它可以如下计算[...]。假设[..]数据被分区为 n 集。我们将函数应用于每个分区,从而生成 n 聚合值。如果通过将函数应用于 n 聚合值而得到的结果与通过将函数应用于整个数据集而得到的结果(没有分区)相同,则可以以分布式方式计算函数。

或者换句话说,它必须是联想和可交换的。然而,如果符合Han et al.,则聚合函数是代数的。

  

[...]它可以通过带有m个参数的代数函数计算(其中m是有界正整数),每个都是通过应用分布式聚合函数获得的。

对于算术平均值,这只是 avg = sum / count 。显然你需要另外携带一个计数。但是,使用全球计数器似乎是一种滥用。 Han et al.描述org.apache.hadoop.mapreduce.Counter如下:

  

一个跟踪地图/减少作业进度的命名计数器。

计数器通常应用于有关作业的统计数据,但不应作为数据处理过程中计算的一部分。

因此,您需要在分区中执行的所有操作都是添加数字并跟踪其计数以及总和(总和,计数);一个简单的方法可以是<sum><separator><count>之类的字符串。

在映射器中,计数始终为1,总和是原始值本身。要减少地图文件,您可以使用组合器并处理聚合,如(sum_1 + ... + sum_n,count_1 + ... + count_n)。这必须在reducer中重复,并通过最终计算 sum / count 完成。 请注意,此方法与使用的密钥无关!

最后,这是一个使用原始API的简单示例,它应该计算洛杉矶的“平均犯罪时间”:

public class Driver extends Configured implements Tool {
    enum Counters {
        DISCARDED_ENTRY
    }

    public static void main(String[] args) throws Exception {
        ToolRunner.run(new Driver(), args);
    }

    public int run(String[] args) throws Exception {
        Configuration configuration = getConf();

        Job job = Job.getInstance(configuration);
        job.setJarByClass(Driver.class);

        job.setMapperClass(Mapper.class);
        job.setMapOutputKeyClass(LongWritable.class);
        job.setMapOutputValueClass(Text.class);

        job.setCombinerClass(Combiner.class);
        job.setReducerClass(Reducer.class);
        job.setOutputKeyClass(LongWritable.class);
        job.setOutputValueClass(Text.class);

        FileInputFormat.addInputPath(job, new Path(args[0]));
        FileOutputFormat.setOutputPath(job, new Path(args[1]));

        return job.waitForCompletion(true) ? 0 : -1;
    }
}

public class Mapper extends org.apache.hadoop.mapreduce.Mapper<
    LongWritable,
    Text,
    LongWritable,
    Text
> {

    @Override
    protected void map(
        LongWritable key,
        Text value,
        org.apache.hadoop.mapreduce.Mapper<
            LongWritable,
            Text,
            LongWritable,
            Text
        >.Context context
    ) throws IOException, InterruptedException {
            // parse the CSV line
            ArrayList<String> values = this.parse(value.toString());

            // validate the parsed values
            if (this.isValid(values)) {

                // fetch the third and the fourth column
                String time = values.get(3);
                String year = values.get(2)
                    .substring(values.get(2).length() - 4);

                // convert time to minutes (e.g. 1542 -> 942)
                int minutes = Integer.parseInt(time.substring(0, 2))
                    * 60 + Integer.parseInt(time.substring(2,4));

                // create the aggregate atom (a/n)
                // with a = time in minutes and n = 1
                context.write(
                    new LongWritable(Integer.parseInt(year)),
                    new Text(Integer.toString(minutes) + ":1")
                );
            } else {
                // invalid line format, so we increment a counter
                context.getCounter(Driver.Counters.DISCARDED_ENTRY)
                    .increment(1);
            }
    }

    protected boolean isValid(ArrayList<String> values) {
        return values.size() > 3 
            && values.get(2).length() == 10 
            && values.get(3).length() == 4;
    }

    protected ArrayList<String> parse(String line) {
        ArrayList<String> values = new ArrayList<>();
        String current = "";
        boolean escaping = false;

        for (int i = 0; i < line.length(); i++){
            char c = line.charAt(i);

            if (c == '"') {
                escaping = !escaping;
            } else if (c == ',' && !escaping) {
                values.add(current);
                current = "";
            } else {
                current += c;
            }
        }

        values.add(current);

        return values;
    }
}

public class Combiner extends org.apache.hadoop.mapreduce.Reducer<
    LongWritable,
    Text,
    LongWritable,
    Text
> {

    @Override
    protected void reduce(
        LongWritable key,
        Iterable<Text> values,
        Context context
    ) throws IOException, InterruptedException {
        Long n = 0l;
        Long a = 0l;
        Iterator<Text> iterator = values.iterator();

        // calculate intermediate aggregates
        while (iterator.hasNext()) {
            String[] atom = iterator.next().toString().split(":");
            a += Long.parseLong(atom[0]);
            n += Long.parseLong(atom[1]);
        }

        context.write(key, new Text(Long.toString(a) + ":" + Long.toString(n)));
    }
}

public class Reducer extends org.apache.hadoop.mapreduce.Reducer<
    LongWritable,
    Text,
    LongWritable,
    Text
> {

    @Override
    protected void reduce(
        LongWritable key, 
        Iterable<Text> values, 
        Context context
    ) throws IOException, InterruptedException {
        Long n = 0l;
        Long a = 0l;
        Iterator<Text> iterator = values.iterator();

        // calculate the finale aggregate
        while (iterator.hasNext()) {
            String[] atom = iterator.next().toString().split(":");
            a += Long.parseLong(atom[0]);
            n += Long.parseLong(atom[1]);
        }

        // cut of seconds
        int average = Math.round(a / n);

        // convert the average minutes back to time
        context.write(
            key,
            new Text(
                Integer.toString(average / 60) 
                    + ":" + Integer.toString(average % 60)
            )
        );
    }
}