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}