2016-11-04 55 views
8

Map<Double, Integer>の加重平均を計算するにはどうすればよいですか。Integer値は、Double値の平均値です。 例えば:地図以下たエレメント:Java 8ストリームで加重平均を計算する

  1. (0.7、100)//値は0.7であり、重量が100
  2. で(0.5、200)
  3. (0.3、300)
  4. (0.0、 400)

私は、Java 8ストリームを使用して次の式を適用することを検討していますが、分子と分母を一緒に計算して同時に保存する方法は不明です。ここで還元を使う方法は?

enter image description here

+3

[Collectors.averagingDouble'](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html#averagingDouble-java.util.function)を確認しましたか。 .ToDoubleFunction-)メソッド? – Aaron

答えて

9

あなたはこの作業のために、独自のコレクターを作成することができます。

static <T> Collector<T,?,Double> averagingWeighted(ToDoubleFunction<T> valueFunction, ToIntFunction<T> weightFunction) { 
    class Box { 
     double num = 0; 
     long denom = 0; 
    } 
    return Collector.of(
      Box::new, 
      (b, e) -> { 
       b.num += valueFunction.applyAsDouble(e) * weightFunction.applyAsInt(e); 
       b.denom += weightFunction.applyAsInt(e); 
      }, 
      (b1, b2) -> { b1.num += b2.num; b1.denom += b2.denom; return b1; }, 
      b -> b.num/b.denom 
      ); 
} 

このカスタムコレクタは、パラメータとして二つの機能を取ります。一つは、指定されたストリーム要素に使用する値を返す関数です( ToDoubleFunctionとして)、もう一方は重みを返します(ToIntFunction)。これは、収集プロセス中に分子と分母を格納するヘルパーローカルクラスを使用します。エントリーが受け入れられるたびに、分子はその値にその重みを掛けた結果で増加し、分母は加重と共に増加する。フィニッシャは、この2つの除算をDoubleとして返します。

使用例は次のようになります。

Map<Double,Integer> map = new HashMap<>(); 
map.put(0.7, 100); 
map.put(0.5, 200); 

double weightedAverage = 
    map.entrySet().stream().collect(averagingWeighted(Map.Entry::getKey, Map.Entry::getValue)); 
+0

これと素晴らしい説明もありがとう。私はカスタムコレクターについてもっと詳しく読むつもりです。 –

0

あなたがマップの加重平均を算出するために、この手順を使用することができます。マップエントリのキーには値が含まれている必要があり、マップエントリの値には重みが含まれている必要があります。

 /** 
    * Calculates the weighted average of a map. 
    * 
    * @throws ArithmeticException If divide by zero happens 
    * @param map A map of values and weights 
    * @return The weighted average of the map 
    */ 
    static Double calculateWeightedAverage(Map<Double, Integer> map) throws ArithmeticException { 
     double num = 0; 
     double denom = 0; 
     for (Map.Entry<Double, Integer> entry : map.entrySet()) { 
      num += entry.getKey() * entry.getValue(); 
      denom += entry.getValue(); 
     } 

     return num/denom; 
    } 

ユースケースを見るためにユニットテストを見ることができます。

 /** 
    * Tests our method to calculate the weighted average. 
    */ 
    @Test 
    public void testAveragingWeighted() { 
     Map<Double, Integer> map = new HashMap<>(); 
     map.put(0.7, 100); 
     map.put(0.5, 200); 
     Double weightedAverage = calculateWeightedAverage(map); 
     Assert.assertTrue(weightedAverage.equals(0.5666666666666667)); 
    } 

あなたはユニットテストのためにこれらの輸入が必要になります。

import org.junit.Assert; 
import org.junit.Test; 

あなたはコードのこれらの輸入が必要になります。

import java.util.HashMap; 
import java.util.Map; 

私はそれが役に立てば幸い。