Como calcular a média de vários números em sequência usando o Java 8 lambda

Se eu tiver collections Point, como calcular a média de x, y usando o stream do Java 8 em uma única iteração.

O exemplo a seguir cria dois streams e itera duas vezes na coleta de input para calcular a média de x e y. É o seu caminho para a média do computador x, y na iteração única usando java 8 lambda:

List points = Arrays.asList(new Point2D.Float(10.0f,11.0f), new Point2D.Float(1.0f,2.9f)); // java 8, iterates twice double xAvg = points.stream().mapToDouble( p -> px).average().getAsDouble(); double yAvg = points.stream().mapToDouble( p -> py).average().getAsDouble(); 

Se você não se importar em usar uma biblioteca adicional, adicionamos suporte para coletores de tupla a jOOλ recentemente.

 Tuple2 avg = points.stream().collect( Tuple.collectors( Collectors.averagingDouble(p -> px), Collectors.averagingDouble(p -> py) ) ); 

No código acima, Tuple.collectors() combina várias instâncias de java.util.stream.Collector em um único Collector que coleta valores individuais em um Tuple .

Isso é muito mais conciso e reutilizável do que qualquer outra solução. O preço que você pagará é que isso atualmente opera em tipos de wrapper, em vez de double primitivo. Acho que teremos que esperar até o Java 10 e projetar o valhalla para a especialização do tipo primitivo em genéricos .

Caso você queira criar o seu próprio, em vez de criar uma dependência, o método relevante se parece com isto:

 static  Collector, Tuple2> collectors( Collector collector1 , Collector collector2 ) { return Collector.of( () -> tuple( collector1.supplier().get() , collector2.supplier().get() ), (a, t) -> { collector1.accumulator().accept(a.v1, t); collector2.accumulator().accept(a.v2, t); }, (a1, a2) -> tuple( collector1.combiner().apply(a1.v1, a2.v1) , collector2.combiner().apply(a1.v2, a2.v2) ), a -> tuple( collector1.finisher().apply(a.v1) , collector2.finisher().apply(a.v2) ) ); } 

Onde Tuple2 é apenas um invólucro simples para dois valores. Você também pode usar AbstractMap.SimpleImmutableEntry ou algo similar.

Eu também detalhei essa técnica em uma resposta a outra pergunta do Stack Overflow .

Escreva um colecionador trivial. Observe a implementação do coletor averagingInt (de Collectors.java):

 public static  Collector averagingInt(ToIntFunction mapper) { return new CollectorImpl<>( () -> new long[2], (a, t) -> { a[0] += mapper.applyAsInt(t); a[1]++; }, (a, b) -> { a[0] += b[0]; a[1] += b[1]; return a; }, a -> (a[1] == 0) ? 0.0d : (double) a[0] / a[1], CH_NOID); } 

Isso pode ser facilmente adaptado para sumr ao longo de dois eixos em vez de um (em uma única passagem), e retornar o resultado em algum suporte simples:

 AverageHolder h = streamOfPoints.collect(averagingPoints()); 

Uma maneira seria definir uma class que agregue os valores x e y dos pontos.

 public class AggregatePoints { private long count = 0L; private double sumX = 0; private double sumY = 0; public double averageX() { return sumX / count; } public double averageY() { return sumY / count; } public void merge(AggregatePoints other) { count += other.count; sumX += other.sumX; sumY += other.sumY; } public void add(Point2D.Float point) { count += 1; sumX += point.getX(); sumY += point.getY(); } } 

Então você acabou de coletar o Stream em uma nova instância:

  AggregatePoints agg = points.stream().collect(AggregatePoints::new, AggregatePoints::add, AggregatePoints::merge); double xAvg = agg.averageX(); double yAvg = agg.averageY(); 

Embora a repetição de duas vezes na lista seja uma solução simples. Eu faria isso a menos que eu realmente tivesse um problema de desempenho.

Com o instantâneo atual de 1.2.0 Javaslang você pode escrever

 import javaslang.collection.List; List.of(points) .unzip(p -> Tuple.of(px, py)) .map((l1, l2) -> Tuple.of(l1.average(), l2.average()))); 

Infelizmente, o Java 1.8.0_31 tem um erro de compilador que não o compila: ‘(

Você recebe um avi Tuple2 que contém os valores computados:

 double xAvg = avgs._1; double yAvg = avgs._2; 

Aqui está o comportamento geral da média ():

 // = 2 List.of(1, 2, 3, 4).average(); // = 2.5 List.of(1.0, 2.0, 3.0, 4.0).average(); // = BigDecimal("0.5") List.of(BigDecimal.ZERO, BigDecimal.ONE).average(); // = UnsupportedOpertationException("average of nothing") List.nil().average(); // = UnsupportedOpertationException("not numeric") List.of("1", "2", "3").average(); // works well with java.util collections final java.util.Set set = new java.util.HashSet<>(); set.add(1); set.add(2); set.add(3); set.add(4); List.of(set).average(); // = 2 

Aqui está a solução mais simples. Você sum todos os valores de x e y usando o método “add” de Point2D e depois usa o método “multiplicar” para obter a média. Código deve ser assim

  int size = points.size(); if (size != 0){ Point2D center = points.parallelStream() .map(Body::getLocation) .reduce( new Point2D(0, 0), (a, b) -> a.add(b) ) .multiply( (double) 1/size ); return center; } 

avarage() é uma operação de redução, então em streams genéricos você usaria reduce() . O problema é que não oferece uma operação de acabamento. Se você quiser calcular a média primeiro resumindo todos os valores e dividindo-os pela contagem, fica um pouco mais complicado.

 List points = Arrays.asList(new Point2D.Float(10.0f,11.0f), new Point2D.Float(1.0f,2.9f)); int counter[] = {1}; Point2D.Float average = points.stream().reduce((avg, point) -> { avg.x += point.x; avg.y += point.y; ++counter[0]; if (counter[0] == points.size()) { avg.x /= points.size(); avg.y /= points.size(); } return avg; }).get(); 

Algumas notas: counter[] tem que ser um array porque as variables ​​usadas pelos lambdas têm que ser efetivamente finais, então não podemos usar um simples int .

Esta versão do reduce() retorna um Optional , então temos que usar get() para obter o valor. Se o stream pode estar vazio, então get() lançaria uma exceção obviamente, mas podemos usar o Optional a nosso favor.

Não tenho certeza se isso funciona com streams paralelos.

Você também pode fazer o seguinte. É provavelmente menos preciso, mas pode ser mais adequado se você tiver muitos números realmente grandes:

 double factor = 1.0 / points.size(); Point2D.Float average = points.stream().reduce(new Point2D.Float(0.0f,0.0f), (avg, point) -> { avg.x += point.x * factor; avg.y += point.y * factor; return avg; }); 

Por outro lado, se a precisão fosse uma grande preocupação, você não usaria float de qualquer maneira;)