001package com.hfg.util.collection;
002
003
004import java.lang.reflect.Constructor;
005import java.util.HashMap;
006import java.util.Map;
007
008//------------------------------------------------------------------------------
009/**
010 Sparse matrix for Numbers that calculated totals and percentages.
011 <div>
012  @author J. Alex Taylor, hairyfatguy.com
013 </div>
014 */
015//------------------------------------------------------------------------------
016// com.hfg Library
017//
018// This library is free software; you can redistribute it and/or
019// modify it under the terms of the GNU Lesser General Public
020// License as published by the Free Software Foundation; either
021// version 2.1 of the License, or (at your option) any later version.
022//
023// This library is distributed in the hope that it will be useful,
024// but WITHOUT ANY WARRANTY; without even the implied warranty of
025// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
026// Lesser General Public License for more details.
027//
028// You should have received a copy of the GNU Lesser General Public
029// License along with this library; if not, write to the Free Software
030// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
031//
032// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
033// jataylor@hairyfatguy.com
034//------------------------------------------------------------------------------
035
036public class NumberMatrix<RK extends Comparable, CK extends Comparable, V extends Number & Comparable> extends SparseMatrix<RK, CK, V>
037{
038   // Cached values
039   private Constructor mValueStringConstructor;
040   private Map<RK, V> mRowTotalMap;
041   private Map<CK, V> mColTotalMap;
042   private V mGrandTotal;
043
044   //##########################################################################
045   // CONSTRUCTORS
046   //##########################################################################
047
048   //--------------------------------------------------------------------------
049   public NumberMatrix()
050   {
051      super();
052   }
053
054   //--------------------------------------------------------------------------
055   public NumberMatrix(int inInitialRowCapacity, int inInitialColCapacity)
056   {
057      super(inInitialRowCapacity, inInitialColCapacity);
058   }
059
060   //##########################################################################
061   // PUBLIC METHODS
062   //##########################################################################
063
064   //--------------------------------------------------------------------------
065
066   /**
067    * Increments the value in the specified cell by the specified amount.
068    *
069    * @param inRowKey        the key of the selected row
070    * @param inColKey        the key of the selected column
071    * @param inIncrementSize the amount by which to increment the value
072    */
073   public void increment(RK inRowKey, CK inColKey, V inIncrementSize)
074   {
075      if (inIncrementSize != null)
076      {
077         V existingValue = get(inRowKey, inColKey);
078
079         V newValue;
080         if (null == existingValue)
081         {
082            newValue = inIncrementSize;
083         }
084         else
085         {
086            double total = existingValue.doubleValue() + inIncrementSize.doubleValue();
087
088            if (null == mValueStringConstructor)
089            {
090               setValueStringConstructorFromValue(existingValue);
091            }
092
093            newValue = instantiateValue(total);
094         }
095
096         put(inRowKey, inColKey, newValue);
097      }
098   }
099
100   //--------------------------------------------------------------------------
101   public V getColTotal(CK inColKey)
102   {
103      V colTotal = null;
104
105      if (null == mColTotalMap
106            && CollectionUtil.hasValues(colKeySet()))
107      {
108         mColTotalMap = new HashMap<>(colKeySet().size());
109         for (CK colKey : colKeySet())
110         {
111            double total = 0.0;
112            for (RK rowKey : rowKeySet())
113            {
114               V value = get(rowKey, colKey);
115               if (null == mValueStringConstructor)
116               {
117                  setValueStringConstructorFromValue(value);
118               }
119
120               if (value != null)
121               {
122                  total += value.doubleValue();
123               }
124            }
125
126            V totalValue = instantiateValue(total);
127
128            mColTotalMap.put(colKey, totalValue);
129         }
130      }
131
132      if (CollectionUtil.hasValues(mColTotalMap))
133      {
134         colTotal = mColTotalMap.get(inColKey);
135      }
136
137      return colTotal;
138   }
139
140   //--------------------------------------------------------------------------
141   public V getRowTotal(RK inRowKey)
142   {
143      V rowTotal = null;
144
145      if (null == mRowTotalMap
146            && CollectionUtil.hasValues(rowKeySet()))
147      {
148         Map<RK, V> map = new HashMap<>(rowKeySet().size());
149         for (RK rowKey : rowKeySet())
150         {
151            double total = 0.0;
152            for (CK colKey : colKeySet())
153            {
154               V value = get(rowKey, colKey);
155               if (null == mValueStringConstructor)
156               {
157                  setValueStringConstructorFromValue(value);
158               }
159
160               if (value != null)
161               {
162                  total += value.doubleValue();
163               }
164            }
165
166            V totalValue = instantiateValue(total);
167
168            map.put(rowKey, totalValue);
169         }
170
171         mRowTotalMap = map;
172      }
173
174      if (CollectionUtil.hasValues(mRowTotalMap))
175      {
176         rowTotal = mRowTotalMap.get(inRowKey);
177      }
178
179      return rowTotal;
180   }
181
182   //--------------------------------------------------------------------------
183   public V getGrandTotal()
184   {
185      if (null == mGrandTotal)
186      {
187         Map<CK, V> colTotalMap = getColTotalMap();
188         if (CollectionUtil.hasValues(colTotalMap))
189         {
190            double total = 0.0;
191            for (V value : colTotalMap.values())
192            {
193               total += value.doubleValue();
194            }
195
196            try
197            {
198               mGrandTotal = instantiateValue(total);
199            }
200            catch (Exception e)
201            {
202               throw new RuntimeException("Problem calculating totals!", e);
203            }
204         }
205      }
206
207      return mGrandTotal;
208   }
209
210   //--------------------------------------------------------------------------
211
212   /**
213    * Calculates the percentage of the total column value represented by the specified cell.
214    *
215    * @param inRowKey the key of the selected row
216    * @param inColKey the key of the selected column
217    * @return the percentage of the total column value represented by the specified cell
218    */
219   public Float getPercentOfCol(RK inRowKey, CK inColKey)
220   {
221      V value = get(inRowKey, inColKey);
222      V colTotal = getColTotal(inColKey);
223
224      return (value != null && colTotal != null && colTotal.floatValue() != 0.0f ? (value.floatValue() * 100f) / colTotal.floatValue() : null);
225   }
226
227   //--------------------------------------------------------------------------
228
229   /**
230    * Calculates the percentage of the total row value represented by the specified cell.
231    *
232    * @param inRowKey the key of the selected row
233    * @param inColKey the key of the selected column
234    * @return the percentage of the total row value represented by the specified cell
235    */
236   public Float getPercentOfRow(RK inRowKey, CK inColKey)
237   {
238      V value = get(inRowKey, inColKey);
239      V rowTotal = getRowTotal(inRowKey);
240
241      return (value != null && rowTotal != null && rowTotal.floatValue() != 0.0f ? (value.floatValue() * 100f) / rowTotal.floatValue() : null);
242   }
243
244   //--------------------------------------------------------------------------
245
246   /**
247    * Calculates the percentage of the grand total (total of all cell values) represented by the specified cell.
248    *
249    * @param inRowKey the key of the selected row
250    * @param inColKey the key of the selected column
251    * @return the percentage of the grand total represented by the specified cell
252    */
253   public Float getOverallPercent(RK inRowKey, CK inColKey)
254   {
255      V value = get(inRowKey, inColKey);
256      V grandTotal = getGrandTotal();
257
258      return (value != null && grandTotal != null && grandTotal.floatValue() != 0.0f ? (value.floatValue() * 100f) / grandTotal.floatValue() : null);
259   }
260
261   //--------------------------------------------------------------------------
262   @Override
263   public void put(RK inRowKey, CK inColKey, V inValue)
264   {
265      clearTotals();
266      super.put(inRowKey, inColKey, inValue);
267   }
268
269   //--------------------------------------------------------------------------
270   @Override
271   public void putRow(RK inRowKey, Map<CK, V> inColMap)
272   {
273      clearTotals();
274      super.putRow(inRowKey, inColMap);
275   }
276
277   //--------------------------------------------------------------------------
278   @Override
279   public void putCol(CK inColKey, Map<RK, V> inRowMap)
280   {
281      clearTotals();
282      super.putCol(inColKey, inRowMap);
283   }
284
285   //--------------------------------------------------------------------------
286   @Override
287   public V remove(RK inRowKey, CK inColKey)
288   {
289      clearTotals();
290      return super.remove(inRowKey, inColKey);
291   }
292
293   //--------------------------------------------------------------------------
294   @Override
295   public void clearRow(RK inRowKey)
296   {
297      clearTotals();
298      super.clearRow(inRowKey);
299   }
300
301   //--------------------------------------------------------------------------
302   @Override
303   public void clearCol(CK inColKey)
304   {
305      clearTotals();
306      super.clearCol(inColKey);
307   }
308
309   //##########################################################################
310   // PRIVATE METHODS
311   //##########################################################################
312
313   //--------------------------------------------------------------------------
314   private void clearTotals()
315   {
316      mRowTotalMap = null;
317      mColTotalMap = null;
318      mGrandTotal = null;
319   }
320
321   //--------------------------------------------------------------------------
322   private void setValueStringConstructorFromValue(V inValue)
323   {
324      try
325      {
326         mValueStringConstructor = inValue.getClass().getConstructor(String.class);
327      }
328      catch (Exception e)
329      {
330         throw new RuntimeException("Problem calculating totals!", e);
331      }
332   }
333
334   //--------------------------------------------------------------------------
335   private V instantiateValue(double inValueAsDouble)
336   {
337      V newValue;
338      try
339      {
340         String doubleString = inValueAsDouble + "";
341         // Prevent Integer constructor from barfing
342         if (doubleString.endsWith(".0"))
343         {
344            doubleString = doubleString.substring(0, doubleString.length() - 2);
345         }
346
347         newValue = (V) mValueStringConstructor.newInstance(doubleString);
348      }
349      catch(Exception e)
350      {
351         throw new RuntimeException("Problem calculating totals!", e);
352      }
353
354      return newValue;
355   }
356
357   //--------------------------------------------------------------------------
358   private Map<CK, V> getColTotalMap()
359   {
360      if (null == mColTotalMap
361            && CollectionUtil.hasValues(colKeySet()))
362      {
363         Map<CK, V> map = new HashMap<>(colKeySet().size());
364         for (CK colKey : colKeySet())
365         {
366            double total = 0.0;
367            for (RK rowKey : rowKeySet())
368            {
369               V value = get(rowKey, colKey);
370               if (null == mValueStringConstructor)
371               {
372                  setValueStringConstructorFromValue(value);
373               }
374
375               if (value != null)
376               {
377                  total += value.doubleValue();
378               }
379            }
380
381            V totalValue = instantiateValue(total);
382
383            map.put(colKey, totalValue);
384         }
385
386         mColTotalMap = map;
387      }
388
389      return mColTotalMap;
390   }
391}