1 package org.djutils.math.means;
2
3 import java.util.Collection;
4 import java.util.Iterator;
5 import java.util.Map;
6 import java.util.Map.Entry;
7 import java.util.function.Function;
8
9 import org.djutils.exceptions.Throw;
10
11 /**
12 * Methods and fields common to all implementations of Mean. Mean implements various kinds of mean. For an excellent discussion
13 * on this subject read <a href=
14 * "https://towardsdatascience.com/on-average-youre-using-the-wrong-average-geometric-harmonic-means-in-data-analysis-2a703e21ea0"
15 * >On Average, You’re Using the Wrong Average: Geometric & Harmonic Means in Data Analysis</a>
16 * <p>
17 * Copyright (c) 2013-2025 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
18 * BSD-style license. See <a href="https://djutils.org/docs/current/djutils/licenses.html">DJUTILS License</a>.
19 * </p>
20 * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
21 * @author <a href="https://www.tudelft.nl/staff/p.knoppers/">Peter Knoppers</a>
22 * @param <MT> mean type
23 * @param <V> value type
24 * @param <W> weight type
25 */
26 public abstract class AbstractMean<MT, V extends Number, W extends Number>
27 {
28 /** Weighted sum of values. Interpretation varies with the kind of mean. */
29 private double weightedSumOfValues;
30
31 /** Sum of weights. */
32 private double sumOfWeights;
33
34 /**
35 * Constructor.
36 */
37 public AbstractMean()
38 {
39 // Nothing to initialize here; the double fields are created with value 0.0 and the unityWeight is initialized to 1.
40 }
41
42 /**
43 * Returns the weighted mean of accumulated data.
44 * @return weighted mean of accumulated data
45 */
46 public abstract double getMean();
47
48 /**
49 * Accumulate some data.
50 * @param value the value to add to the <code>weightedSumOfValues</code>
51 * @param weight the weight to assign to the <code>value</code>
52 */
53 final void increment(final double value, final double weight)
54 {
55 this.weightedSumOfValues += value;
56 this.sumOfWeights += weight;
57 }
58
59 /**
60 * Returns the weighted sum of available data. Meaning varies per type of mean.
61 * @return weighted sum of accumulated data
62 */
63 public final double getSum()
64 {
65 return this.weightedSumOfValues;
66 }
67
68 /**
69 * Returns the sum of the weights.
70 * @return sum of the weights
71 */
72 public final double getSumOfWeights()
73 {
74 return this.sumOfWeights;
75 }
76
77 /**
78 * Adds a value with weight.
79 * @param value the value
80 * @param weight the weight
81 * @return for method chaining
82 */
83 public final AbstractMean<MT, V, W> add(final V value, final W weight)
84 {
85 return addImpl(value, weight);
86 }
87
88 /**
89 * Adds a value with weight.
90 * @param value the value
91 * @param weight the weight
92 * @return for method chaining
93 */
94 protected abstract AbstractMean<MT, V, W> addImpl(V value, Number weight);
95
96 /** Unity weight. */
97 private final Number unityWeight = Integer.valueOf(1);
98
99 /**
100 * Add a value with weight 1.
101 * @param value the value
102 * @return for method chaining
103 */
104 public final AbstractMean<MT, V, W> add(final V value)
105 {
106 return addImpl(value, this.unityWeight);
107 }
108
109 /**
110 * Adds weighted values. Note that iteration order is pivotal in correct operations. This method should not be used with
111 * instances of {@code HashMap} or {@code HashSet}.
112 * @param values values
113 * @param weights weights
114 * @return for method chaining
115 * @throws IllegalArgumentException if the number of values is not equal to the number of weights
116 */
117 public final AbstractMean<MT, V, W> add(final Iterable<V> values, final Iterable<W> weights) throws IllegalArgumentException
118 {
119 Iterator<V> itV = values.iterator();
120 Iterator<W> itW = weights.iterator();
121 while (itV.hasNext())
122 {
123 Throw.when(!itW.hasNext(), IllegalArgumentException.class, "Unequal number of values and weights.");
124 addImpl(itV.next(), itW.next());
125 }
126 Throw.when(itW.hasNext(), IllegalArgumentException.class, "Unequal number of values and weights.");
127 return this;
128 }
129
130 /**
131 * Adds weighted values.
132 * @param values values
133 * @param weights weights
134 * @return for method chaining
135 * @throws IllegalArgumentException if the number of values is not equal to the number of weights
136 */
137 public final AbstractMean<MT, V, W> add(final V[] values, final W[] weights) throws IllegalArgumentException
138 {
139 Throw.when(values.length != weights.length, IllegalArgumentException.class, "Unequal number of values and weights.");
140 for (int i = 0; i < values.length; i++)
141 {
142 addImpl(values[i], weights[i]);
143 }
144 return this;
145 }
146
147 /**
148 * Adds each key value from a map weighted with the mapped to value.
149 * @param map map
150 * @return for method chaining
151 */
152 public final AbstractMean<MT, V, W> add(final Map<V, W> map)
153 {
154 for (Entry<V, W> entry : map.entrySet())
155 {
156 addImpl(entry.getKey(), entry.getValue());
157 }
158 return this;
159 }
160
161 /**
162 * Adds each value with a weight obtained by calling the provided <code>weights</code> function.
163 * @param collection values
164 * @param weights weights
165 * @return for method chaining
166 */
167 public final AbstractMean<MT, V, W> add(final Collection<V> collection, final Function<V, W> weights)
168 {
169 for (V v : collection)
170 {
171 addImpl(v, weights.apply(v));
172 }
173 return this;
174 }
175
176 /**
177 * Adds each value (obtained by calling the <code>values</code> function on each object in a Collection) with a weight
178 * (obtained by calling the <code> weights</code> function on the same object from the Collection).
179 * @param collection collection of source objects
180 * @param values values
181 * @param weights weights
182 * @param <S> type of source object
183 * @return for method chaining
184 */
185 public final <S> AbstractMean<MT, V, W> add(final Collection<S> collection, final Function<S, V> values,
186 final Function<S, W> weights)
187 {
188 for (S s : collection)
189 {
190 addImpl(values.apply(s), weights.apply(s));
191 }
192 return this;
193 }
194
195 /**
196 * Add values with weight 1.
197 * @param values the values to add
198 * @return for method chaining
199 */
200 public final AbstractMean<MT, V, W> add(final Iterable<V> values)
201 {
202 Iterator<V> itV = values.iterator();
203 while (itV.hasNext())
204 {
205 addImpl(itV.next(), this.unityWeight);
206 }
207 return this;
208 }
209
210 /**
211 * Add values with weight 1.
212 * @param values the values to add
213 * @return for method chaining
214 */
215 public final AbstractMean<MT, V, W> add(final V[] values)
216 {
217 for (int i = 0; i < values.length; i++)
218 {
219 addImpl(values[i], this.unityWeight);
220 }
221 return this;
222 }
223
224 }