001package com.hfg.util.collection; 002 003import java.io.*; 004import java.util.ArrayList; 005import java.util.Collections; 006import java.util.HashMap; 007import java.util.LinkedHashSet; 008import java.util.List; 009import java.util.Map; 010import java.util.Set; 011 012import com.hfg.util.CompareUtil; 013import com.hfg.util.StringUtil; 014 015 016//------------------------------------------------------------------------------ 017/** 018 Base matrix class. 019 <div> 020 @author J. Alex Taylor, hairyfatguy.com 021 </div> 022 */ 023//------------------------------------------------------------------------------ 024// com.hfg Library 025// 026// This library is free software; you can redistribute it and/or 027// modify it under the terms of the GNU Lesser General Public 028// License as published by the Free Software Foundation; either 029// version 2.1 of the License, or (at your option) any later version. 030// 031// This library is distributed in the hope that it will be useful, 032// but WITHOUT ANY WARRANTY; without even the implied warranty of 033// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 034// Lesser General Public License for more details. 035// 036// You should have received a copy of the GNU Lesser General Public 037// License along with this library; if not, write to the Free Software 038// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 039// 040// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com 041// jataylor@hairyfatguy.com 042//------------------------------------------------------------------------------ 043 044public abstract class AbstractSparseMatrix<RK extends Comparable, CK extends Comparable, V extends Comparable> implements Cloneable 045{ 046 047 private LinkedHashSet<RK> mOrderedRowKeySet; 048 private LinkedHashSet<CK> mOrderedColKeySet; 049 private Map<RK, Map<CK, V>> mMatrixMap; 050 051 private int mInitialColCapacity; 052 053 private static int sDefaultInitialCapacity = 99; 054 055 //########################################################################## 056 // CONSTRUCTORS 057 //########################################################################## 058 059 060 //-------------------------------------------------------------------------- 061 public AbstractSparseMatrix() 062 { 063 this(sDefaultInitialCapacity, sDefaultInitialCapacity); 064 } 065 066 //-------------------------------------------------------------------------- 067 public AbstractSparseMatrix(int inInitialRowCapacity, int inInitialColCapacity) 068 { 069 mOrderedRowKeySet = new LinkedHashSet<>(inInitialRowCapacity); 070 mOrderedColKeySet = new LinkedHashSet<>(inInitialColCapacity); 071 mMatrixMap = new HashMap<>(inInitialRowCapacity); 072 mInitialColCapacity = inInitialColCapacity; 073 } 074 075 //########################################################################## 076 // PUBLIC METHODS 077 //########################################################################## 078 079 080 //-------------------------------------------------------------------------- 081 public void addRow(RK inRowKey) 082 { 083 mOrderedRowKeySet.add(inRowKey); 084 } 085 086 //-------------------------------------------------------------------------- 087 public void addCol(CK inColKey) 088 { 089 mOrderedColKeySet.add(inColKey); 090 } 091 092 //-------------------------------------------------------------------------- 093 public void put(RK inRowKey, CK inColKey, V inValue) 094 { 095 mOrderedRowKeySet.add(inRowKey); 096 mOrderedColKeySet.add(inColKey); 097 098 Map<CK, V> colMap = mMatrixMap.get(inRowKey); 099 if (null == colMap) 100 { 101 colMap = new HashMap<>(mInitialColCapacity); 102 mMatrixMap.put(inRowKey, colMap); 103 } 104 colMap.put(inColKey, inValue); 105 } 106 107 //-------------------------------------------------------------------------- 108 public void putRow(RK inRowKey, Map<CK, V> inColMap) 109 { 110 if (CollectionUtil.hasValues(inColMap)) 111 { 112 for (CK colKey : inColMap.keySet()) 113 { 114 put(inRowKey, colKey, inColMap.get(colKey)); 115 } 116 } 117 } 118 119 //-------------------------------------------------------------------------- 120 public void putCol(CK inColKey, Map<RK, V> inRowMap) 121 { 122 if (CollectionUtil.hasValues(inRowMap)) 123 { 124 for (RK rowKey : inRowMap.keySet()) 125 { 126 put(rowKey, inColKey, inRowMap.get(rowKey)); 127 } 128 } 129 } 130 131 //-------------------------------------------------------------------------- 132 public V get(RK inRowKey, CK inColKey) 133 { 134 V value = null; 135 136 Map<CK, V> colMap = mMatrixMap.get(inRowKey); 137 if (colMap != null) 138 { 139 value = colMap.get(inColKey); 140 } 141 142 return value; 143 } 144 145 //-------------------------------------------------------------------------- 146 public V remove(RK inRowKey, CK inColKey) 147 { 148 V removedValue = null; 149 150 Map<CK, V> colMap = mMatrixMap.get(inRowKey); 151 if (colMap != null) 152 { 153 removedValue = colMap.remove(inColKey); 154 } 155 156 return removedValue; 157 } 158 159 //-------------------------------------------------------------------------- 160 public void clearRow(RK inRowKey) 161 { 162 if (containsRow(inRowKey)) 163 { 164 for (CK colKey : colKeySet()) 165 { 166 remove(inRowKey, colKey); 167 } 168 } 169 } 170 171 //-------------------------------------------------------------------------- 172 public void clearCol(CK inColKey) 173 { 174 if (containsCol(inColKey)) 175 { 176 for (RK rowKey : rowKeySet()) 177 { 178 remove(rowKey, inColKey); 179 } 180 } 181 } 182 183 //-------------------------------------------------------------------------- 184 public void trimToSize() 185 { 186 LinkedHashSet orderedColKeySet = new LinkedHashSet<>(mOrderedColKeySet.size(), 1.0f); 187 orderedColKeySet.addAll(mOrderedColKeySet); 188 mOrderedColKeySet = orderedColKeySet; 189 190 LinkedHashSet orderedRowKeySet = new LinkedHashSet<>(mOrderedRowKeySet.size(), 1.0f); 191 orderedRowKeySet.addAll(mOrderedRowKeySet); 192 mOrderedRowKeySet = orderedRowKeySet; 193 194 // Not sure how this transfer can be done more efficiently 195 Map<RK, Map<CK, V>> compactedMap = new HashMap<>(mMatrixMap.size(), 1.0f); 196 compactedMap.putAll(mMatrixMap); 197 mMatrixMap = compactedMap; 198 199 for (RK rowKey : mMatrixMap.keySet()) 200 { 201 Map<CK, V> uncompactedValueMap = mMatrixMap.get(rowKey); 202 if (uncompactedValueMap != null) 203 { 204 Map<CK, V> compactedValueMap = new HashMap<>(uncompactedValueMap.size(), 1.0f); 205 compactedValueMap.putAll(uncompactedValueMap); 206 mMatrixMap.put(rowKey, compactedValueMap); 207 } 208 } 209 } 210 211 //-------------------------------------------------------------------------- 212 @Override 213 public AbstractSparseMatrix<RK, CK, V> clone() 214 { 215 AbstractSparseMatrix<RK, CK, V> clone; 216 try 217 { 218 clone = (AbstractSparseMatrix<RK, CK, V>) super.clone(); 219 } 220 catch (CloneNotSupportedException e) 221 { 222 throw new RuntimeException(e); 223 } 224 225 clone.mOrderedRowKeySet = new LinkedHashSet<>(mOrderedRowKeySet); 226 clone.mOrderedColKeySet = new LinkedHashSet<>(mOrderedColKeySet); 227 228 clone.mMatrixMap = new HashMap<>(mMatrixMap.size()); 229 for (RK rowKey : mMatrixMap.keySet()) 230 { 231 Map<CK, V> colMap = mMatrixMap.get(rowKey); 232 if (colMap != null) 233 { 234 Map<CK, V> clonedColMap = new HashMap<>(colMap.size()); 235 for (CK colKey : colMap.keySet()) 236 { 237 clonedColMap.put(colKey, colMap.get(colKey)); 238 } 239 240 clone.mMatrixMap.put(rowKey, clonedColMap); 241 } 242 else 243 { 244 clone.mMatrixMap.put(rowKey, null); 245 } 246 247 } 248 249 return clone; 250 } 251 252 //-------------------------------------------------------------------------- 253 public int size() 254 { 255 int size = 0; 256 for (Map<CK, V> colMap : mMatrixMap.values()) 257 { 258 if (colMap != null) 259 { 260 size += colMap.size(); 261 } 262 } 263 264 return size; 265 } 266 267 //-------------------------------------------------------------------------- 268 public MatrixCell<RK, CK, V> getCellWithSmallestValue() 269 { 270 return getCellWithSmallestValue(true); 271 } 272 273 //-------------------------------------------------------------------------- 274 public MatrixCell<RK, CK, V> getNonIdentityCellWithSmallestValue() 275 { 276 return getCellWithSmallestValue(false); 277 } 278 279 //-------------------------------------------------------------------------- 280 @Override 281 public String toString() 282 { 283 String outString; 284 try 285 { 286 ByteArrayOutputStream outStream = new ByteArrayOutputStream(); 287 toString(outStream); 288 outStream.close(); 289 outString = outStream.toString(); 290 } 291 catch (IOException e) 292 { 293 throw new RuntimeException(e); 294 } 295 296 return outString; 297 } 298 299 //-------------------------------------------------------------------------- 300 public void toString(OutputStream inStream) 301 throws IOException 302 { 303 Writer writer = new PrintWriter(inStream); 304 305 int maxRowKeyLength = getMaxRowKeyLength(); 306 307 // Build a map of columm string lengths 308 Map<CK, Integer> maxColValueLengthMap = new HashMap<>(colKeySet().size()); 309 for (CK colKey : colKeySet()) 310 { 311 Integer maxColValueLength = null; 312 for (RK rowKey : rowKeySet()) 313 { 314 V value = get(rowKey, colKey); 315 if (value != null) 316 { 317 String stringValue = getValueString(value); 318 if (null == maxColValueLength 319 || stringValue.length() > maxColValueLength) 320 { 321 maxColValueLength = stringValue.length(); 322 } 323 } 324 } 325 326 maxColValueLengthMap.put(colKey, maxColValueLength); 327 } 328 329 // Header line 330 writer.write(StringUtil.polyChar(' ', maxRowKeyLength) + " "); 331 for (CK colKey : colKeySet()) 332 { 333 int colKeyLength = colKey.toString().length(); 334 Integer maxColValueLength = maxColValueLengthMap.get(colKey); 335 if (null == maxColValueLength 336 || colKeyLength > maxColValueLength) 337 { 338 maxColValueLength = colKeyLength; 339 } 340 341 writer.write(String.format("%" + maxColValueLength + "." + maxColValueLength + "s ", colKey.toString())); 342 } 343 writer.write("\n"); 344 345 346 for (RK rowKey : rowKeySet()) 347 { 348 writer.write(String.format("%" + maxRowKeyLength + "." + maxRowKeyLength + "s ", rowKey.toString())); 349 350 for (CK colKey : colKeySet()) 351 { 352 int colKeyLength = colKey.toString().length(); 353 Integer maxColValueLength = maxColValueLengthMap.get(colKey); 354 if (null == maxColValueLength 355 || colKeyLength > maxColValueLength) 356 { 357 maxColValueLength = colKeyLength; 358 } 359 360 V value = get(rowKey, colKey); 361 if (value != null) 362 { 363 String stringValue = getValueString(value); 364 365 writer.write(String.format("%" + maxColValueLength + "." + maxColValueLength + "s ", stringValue)); 366 } 367 else 368 { 369 writer.write(StringUtil.polyChar(' ', maxColValueLength) + " "); 370 } 371 372 } 373 writer.write("\n"); 374 } 375 376 writer.flush(); 377 } 378 379 //-------------------------------------------------------------------------- 380 private String getValueString(V inValue) 381 { 382 String stringValue; 383 if (inValue instanceof Double 384 || inValue instanceof Float) 385 { 386 stringValue = String.format("%.2f", inValue); 387 } 388 else 389 { 390 stringValue = inValue.toString(); 391 } 392 393 return stringValue; 394 } 395 396 //########################################################################## 397 // PROTECTED METHODS 398 //########################################################################## 399 400 //-------------------------------------------------------------------------- 401 protected Set<RK> rowKeySet() 402 { 403 return Collections.unmodifiableSet(mOrderedRowKeySet); 404 } 405 406 //-------------------------------------------------------------------------- 407 protected boolean containsRow(RK inRowKey) 408 { 409 return mOrderedRowKeySet.contains(inRowKey); 410 } 411 412 //-------------------------------------------------------------------------- 413 protected Map<CK, V> removeRow(RK inRowKey) 414 { 415 mOrderedRowKeySet.remove(inRowKey); 416 return mMatrixMap.remove(inRowKey); 417 } 418 419 //-------------------------------------------------------------------------- 420 /** 421 Changes the rowkey inOldKey to inNewKey. 422 */ 423 protected void changeRowKey(RK inOldKey, RK inNewKey) 424 { 425 if (mOrderedRowKeySet != null) 426 { 427 List<RK> rowKeyList = new ArrayList<>(mOrderedRowKeySet); 428 int index = rowKeyList.indexOf(inOldKey); 429 if (index >= 0) 430 { 431 rowKeyList.remove(inOldKey); 432 rowKeyList.add(index, inNewKey); 433 mOrderedRowKeySet = new LinkedHashSet<>(rowKeyList); 434 435 mMatrixMap.put(inNewKey, mMatrixMap.remove(inOldKey)); 436 } 437 } 438 } 439 440 //-------------------------------------------------------------------------- 441 protected Set<CK> colKeySet() 442 { 443 return Collections.unmodifiableSet(mOrderedColKeySet); 444 } 445 446 //-------------------------------------------------------------------------- 447 protected Map<CK, V> getRow(RK inRowKey) 448 { 449 Map<CK, V> colMap = mMatrixMap.get(inRowKey); 450 return (colMap != null ? Collections.unmodifiableMap(colMap) : null); 451 } 452 453 //-------------------------------------------------------------------------- 454 protected Map<RK, V> getCol(CK inColKey) 455 { 456 Map<RK, V> rowMap = new OrderedMap<>(rowKeySet().size()); 457 for (RK rowKey : rowKeySet()) 458 { 459 rowMap.put(rowKey, mMatrixMap.get(rowKey).get(inColKey)); 460 } 461 462 return rowMap; 463 } 464 465 //-------------------------------------------------------------------------- 466 protected boolean containsCol(CK inColKey) 467 { 468 return mOrderedColKeySet.contains(inColKey); 469 } 470 471 //-------------------------------------------------------------------------- 472 protected void removeCol(CK inColKey) 473 { 474 if (mOrderedColKeySet.remove(inColKey)) 475 { 476 for (Map<CK, V> colMap : mMatrixMap.values()) 477 { 478 colMap.remove(inColKey); 479 } 480 } 481 } 482 483 //-------------------------------------------------------------------------- 484 protected void removeCols(Set<CK> inColKeys) 485 { 486 if (inColKeys != null 487 && mOrderedColKeySet.removeAll(inColKeys)) 488 { 489 for (Map<CK, V> colMap : mMatrixMap.values()) 490 { 491 for (CK colKey : inColKeys) 492 { 493 colMap.remove(colKey); 494 } 495 } 496 } 497 } 498 499 //-------------------------------------------------------------------------- 500 /** 501 Changes the colkey inOldKey to inNewKey. 502 */ 503 protected void changeColKey(CK inOldKey, CK inNewKey) 504 { 505 if (mOrderedColKeySet != null) 506 { 507 List<CK> colKeyList = new ArrayList<>(mOrderedColKeySet); 508 int index = colKeyList.indexOf(inOldKey); 509 if (index >= 0) 510 { 511 colKeyList.remove(inOldKey); 512 colKeyList.add(index, inNewKey); 513 mOrderedColKeySet = new LinkedHashSet<>(colKeyList); 514 515 for (RK rowKey : mMatrixMap.keySet()) 516 { 517 Map<CK, V> colMap = mMatrixMap.get(rowKey); 518 if (colMap != null) 519 { 520 if (colMap.containsKey(inOldKey)) 521 { 522 colMap.put(inNewKey, colMap.remove(inOldKey)); 523 } 524 } 525 } 526 } 527 } 528 } 529 530 531 //-------------------------------------------------------------------------- 532 private int getMaxRowKeyLength() 533 { 534 int maxLength = 0; 535 if (CollectionUtil.hasValues(mOrderedRowKeySet)) 536 { 537 for (RK key : mOrderedRowKeySet) 538 { 539 if (key != null) 540 { 541 int keyLength = key.toString().length(); 542 if (keyLength > maxLength) 543 { 544 maxLength = keyLength; 545 } 546 } 547 } 548 } 549 550 return maxLength; 551 } 552 553 //-------------------------------------------------------------------------- 554 private int getMaxColKeyLength() 555 { 556 int maxLength = 0; 557 if (CollectionUtil.hasValues(mOrderedColKeySet)) 558 { 559 for (CK key : mOrderedColKeySet) 560 { 561 if (key != null) 562 { 563 int keyLength = key.toString().length(); 564 if (keyLength > maxLength) 565 { 566 maxLength = keyLength; 567 } 568 } 569 } 570 } 571 572 return maxLength; 573 } 574 575 //-------------------------------------------------------------------------- 576 private MatrixCell<RK, CK, V> getCellWithSmallestValue(boolean inIncludeIdentityCells) 577 { 578 V smallestValue = null; 579 RK targetRow = null; 580 CK targetCol = null; 581 582 for (RK rowKey : mMatrixMap.keySet()) 583 { 584 Map<CK, V> colMap = mMatrixMap.get(rowKey); 585 if (colMap != null) 586 { 587 for (CK colKey : colMap.keySet()) 588 { 589 if (inIncludeIdentityCells 590 || ! rowKey.equals(colKey)) 591 { 592 V value = colMap.get(colKey); 593 if (value != null) 594 { 595 int comparison = 0; 596 if (null == smallestValue) 597 { 598 comparison = 1; 599 } 600 else 601 { 602 comparison = CompareUtil.compare(smallestValue, value); 603 604 if (0 == comparison) 605 { 606 comparison = CompareUtil.compare(targetCol.toString(), colKey.toString()); 607 } 608 609 if (0 == comparison) 610 { 611 comparison = CompareUtil.compare(targetRow.toString(), rowKey.toString()); 612 } 613 } 614 615 if (1 == comparison) 616 { 617 smallestValue = value; 618 targetRow = rowKey; 619 targetCol = colKey; 620 } 621 } 622 } 623 } 624 } 625 } 626// System.out.println(smallestValue + " ( " + targetRow + " " + targetCol); 627 628 return smallestValue != null ? new MatrixCell<>(targetRow, targetCol, smallestValue) : null; 629 } 630 631}