001package com.hfg.util.collection; 002 003 004import java.io.IOException; 005import java.io.Writer; 006import java.util.*; 007 008import com.hfg.exception.ProgrammingException; 009 010//------------------------------------------------------------------------------ 011/** 012 A symmetric matrix (a square matrix with values that are symmetric with respect to the diagonal). 013 <div> 014 Ex:<pre> 015 1 5 -3 016 5 2 9 017 -3 9 8 018 </pre> 019 </div> 020 <div> 021 See <a href='http://en.wikipedia.org/wiki/Symmetric_matrix'>http://en.wikipedia.org/wiki/Symmetric_matrix</a> 022 </div> 023 * @author J. Alex Taylor, hairyfatguy.com 024 */ 025//------------------------------------------------------------------------------ 026// com.hfg Library 027// 028// This library is free software; you can redistribute it and/or 029// modify it under the terms of the GNU Lesser General Public 030// License as published by the Free Software Foundation; either 031// version 2.1 of the License, or (at your option) any later version. 032// 033// This library is distributed in the hope that it will be useful, 034// but WITHOUT ANY WARRANTY; without even the implied warranty of 035// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 036// Lesser General Public License for more details. 037// 038// You should have received a copy of the GNU Lesser General Public 039// License along with this library; if not, write to the Free Software 040// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 041// 042// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com 043// jataylor@hairyfatguy.com 044//------------------------------------------------------------------------------ 045 046public class SymmetricMatrix<K extends Comparable, V extends Comparable> implements Cloneable 047{ 048 protected List<MatrixRow> mMatrix; 049 050 private HashMap<K, MatrixRow> mKeyMap; 051 052 // Cached values 053 private Integer mSize; 054 private SortedList<MatrixCell<K, K, V>> mSmallestValues; 055 private boolean mSmallestValuesIncludeIdentity; 056 057 private static int sDefaultInitialCapacity = 99; 058 private static int sSmallestValuesCapacity = 1000; 059 private static int sSmallestValuesRetentionSize = 750; 060 061 062 //########################################################################## 063 // CONSTRUCTORS 064 //########################################################################## 065 066 //-------------------------------------------------------------------------- 067 public SymmetricMatrix() 068 { 069 this(sDefaultInitialCapacity); 070 } 071 072 //-------------------------------------------------------------------------- 073 public SymmetricMatrix(int inInitialCapacity) 074 { 075 mKeyMap = new HashMap<>(inInitialCapacity); 076 mMatrix = new ArrayList<>(inInitialCapacity); 077 } 078 079 //########################################################################## 080 // PUBLIC METHODS 081 //########################################################################## 082 083 //-------------------------------------------------------------------------- 084 public void put(K inKey1, K inKey2, V inValue) 085 { 086 MatrixRow matrixRow1 = mKeyMap.get(inKey1); 087 if (null == matrixRow1) 088 { 089 matrixRow1 = innerAddKey(inKey1); 090 } 091 092 MatrixRow matrixRow2 = mKeyMap.get(inKey2); 093 if (null == matrixRow2) 094 { 095 matrixRow2 = innerAddKey(inKey2); 096 } 097 098 099 MatrixRow rowWithCell = (matrixRow1.getIndex() < matrixRow2.getIndex() ? matrixRow2 : matrixRow1); 100 rowWithCell.getData().set((matrixRow1 == rowWithCell ? matrixRow2 : matrixRow1).getIndex(), inValue); 101 102 // If there is a smallest value cache, see if the new value should be added 103 if (mSmallestValues != null 104 && inValue.compareTo(mSmallestValues.last().getValue()) <= 0 105 && (mSmallestValuesIncludeIdentity || inKey1 != inKey2)) 106 { 107 mSmallestValues.add(new MatrixCell<>(inKey1, inKey2, inValue)); 108 if (mSmallestValues.size() > sSmallestValuesCapacity - 2) 109 { 110 // Truncate the list 111 mSmallestValues.truncate(sSmallestValuesRetentionSize); 112 } 113 } 114 } 115 116 //-------------------------------------------------------------------------- 117 public V get(K inKey1, K inKey2) 118 { 119 MatrixRow matrixRow1 = mKeyMap.get(inKey1); 120 if (null == matrixRow1) 121 { 122 throw new RuntimeException("'" + inKey1 + "' is not a valid matrix key!"); 123 } 124 125 MatrixRow matrixRow2 = mKeyMap.get(inKey2); 126 if (null == matrixRow2) 127 { 128 throw new RuntimeException("'" + inKey2 + "' is not a valid matrix key!"); 129 } 130 131 MatrixRow rowWithCell = (matrixRow1.getIndex() < matrixRow2.getIndex() ? matrixRow2 : matrixRow1); 132 V value = rowWithCell.getData().get((matrixRow1 == rowWithCell ? matrixRow2 : matrixRow1).getIndex()); 133 134 return value; 135 } 136 137 //-------------------------------------------------------------------------- 138 // TODO: 139 public void toString(Writer inWriter) 140 throws IOException 141 { 142 int maxKeyLength = getMaxKeyLength(); 143 String keyFormat = "%-" + maxKeyLength + "." + maxKeyLength + "s"; 144 145 Set<K> orderedKeys = orderedKeySet(); 146 for (K key1 : orderedKeys) 147 { 148 inWriter.append(String.format(keyFormat, key1)); 149 for (K key2 : orderedKeys) 150 { 151 inWriter.append(String.format(" %.4f", get(key1, key2))); 152 153 if (key1 == key2) 154 { 155 break; 156 } 157 } 158 159 inWriter.append(System.getProperty("line.separator")); 160 } 161 } 162 163 //-------------------------------------------------------------------------- 164 /** 165 * Returns the keys in no guaranteed order. 166 * @return a Set containing the matrix keys 167 */ 168 public Set<K> keySet() 169 { 170 return mKeyMap.keySet(); 171 } 172 173 //-------------------------------------------------------------------------- 174 /** 175 * Returns the keys in addition order. 176 * @return a Set containing the matrix keys in the order of addition 177 */ 178 public Set<K> orderedKeySet() 179 { 180 OrderedSet<K> keySet = null; 181 182 if (CollectionUtil.hasValues(mMatrix)) 183 { 184 keySet = new OrderedSet<>(mMatrix.size()); 185 for (MatrixRow matrixRow : mMatrix) 186 { 187 keySet.add(matrixRow.getKey()); 188 } 189 } 190 191 return keySet; 192 } 193 194 //-------------------------------------------------------------------------- 195 public int numKeys() 196 { 197 return (mKeyMap != null ? mKeyMap.size() : 0); 198 } 199 200 //-------------------------------------------------------------------------- 201 public boolean addKey(K inKey) 202 { 203 boolean added = false; 204 if (! mKeyMap.containsKey(inKey)) 205 { 206 innerAddKey(inKey); 207 208 added = true; 209 } 210 211 return added; 212 } 213 214 //-------------------------------------------------------------------------- 215 public boolean containsKey(K inKey) 216 { 217 return mKeyMap.containsKey(inKey); 218 } 219 220 //-------------------------------------------------------------------------- 221 // TODO: Should this return Map<K, V> ? 222 public void removeKey(K inKey) 223 { 224 MatrixRow matrixRow = mKeyMap.get(inKey); 225 if (matrixRow != null) 226 { 227 int removedIndex = matrixRow.getIndex(); 228 229 mKeyMap.remove(inKey); 230 mMatrix.remove(removedIndex); 231 for (int i = removedIndex; i < mMatrix.size(); i++) 232 { 233 MatrixRow matrixRowToAdjust = mMatrix.get(i); 234 matrixRowToAdjust.decrementIndex(); 235 matrixRowToAdjust.getData().remove(removedIndex); 236 } 237 238 // If any of the cached smallest values are associated with the removed key, remove them 239 if (mSmallestValues != null) 240 { 241 for (int i = 0; i < mSmallestValues.size(); i++) 242 { 243 MatrixCell<K, K, V> matrixCell = mSmallestValues.get(i); 244 if (matrixCell.getRowKey().equals(inKey) 245 || matrixCell.getColKey().equals(inKey)) 246 { 247 mSmallestValues.remove(i--); 248 } 249 } 250 } 251 252 // Clear the cached matrix size 253 mSize = null; 254 } 255 } 256 257 //-------------------------------------------------------------------------- 258 public void removeKeys(Collection<K> inKeys) 259 { 260 if (CollectionUtil.hasValues(inKeys)) 261 { 262 for (K key : inKeys) 263 { 264 removeKey(key); 265 } 266 } 267 } 268 269 //-------------------------------------------------------------------------- 270 public void changeKey(K inOldKey, K inNewKey) 271 { 272 MatrixRow matrixRow = mKeyMap.get(inOldKey); 273 if (matrixRow != null) 274 { 275 matrixRow.setKey(inNewKey); 276 mKeyMap.remove(inOldKey); 277 mKeyMap.put(inNewKey, matrixRow); 278 279 // If any of the cached smallest values are associated with the changed key, change them 280 if (mSmallestValues != null) 281 { 282 for (int i = 0; i < mSmallestValues.size(); i++) 283 { 284 MatrixCell<K, K, V> matrixCell = mSmallestValues.get(i); 285 if (matrixCell.getRowKey().equals(inOldKey)) 286 { 287 matrixCell.setRowKey(inNewKey); 288 } 289 else if (matrixCell.getColKey().equals(inOldKey)) 290 { 291 matrixCell.setColKey(inNewKey); 292 } 293 } 294 } 295 } 296 } 297 298 //-------------------------------------------------------------------------- 299 public MatrixCell<K, K, V> getCellWithSmallestValue() 300 { 301 return getCellWithSmallestValue(true); 302 } 303 304 //-------------------------------------------------------------------------- 305 public MatrixCell<K, K, V> getNonIdentityCellWithSmallestValue() 306 { 307 return getCellWithSmallestValue(false); 308 } 309 310 //-------------------------------------------------------------------------- 311 @Override 312 public SymmetricMatrix<K, V> clone() 313 { 314 SymmetricMatrix<K, V> clone; 315 try 316 { 317 clone = (SymmetricMatrix<K, V>) super.clone(); 318 } 319 catch (CloneNotSupportedException e) 320 { 321 throw new ProgrammingException(e); 322 } 323 324 clone.mKeyMap = new HashMap<>(mKeyMap.size()); 325 clone.mMatrix = new ArrayList<>(mMatrix.size()); 326 for (MatrixRow matrixRow : mMatrix) 327 { 328 // The map values and the array values are the same 329 // objects and we need to keep it that way 330 matrixRow = matrixRow.clone(); 331 clone.mMatrix.add(matrixRow); 332 clone.mKeyMap.put(matrixRow.getKey(), matrixRow); 333 } 334 335 return clone; 336 } 337 338 //-------------------------------------------------------------------------- 339 public int size() 340 { 341 if (null == mSize) 342 { 343 int size = 0; 344 if (CollectionUtil.hasValues(mKeyMap)) 345 { 346 int numKeys = numKeys(); 347 size = ((numKeys * numKeys - numKeys) / 2) + numKeys; 348 } 349 350 mSize = size; 351 } 352 353 return mSize; 354 } 355 356 //-------------------------------------------------------------------------- 357 protected int getIndexForKey(K inKey) 358 { 359 MatrixRow matrixRow = mKeyMap.get(inKey); 360 return (matrixRow != null ? matrixRow.getIndex() : -1); 361 } 362 363 //########################################################################## 364 // PRIVATE METHODS 365 //########################################################################## 366 367 //-------------------------------------------------------------------------- 368 private MatrixRow innerAddKey(K inKey) 369 { 370 // Create and initialize a matrix row 371 MatrixRow matrixRow = new MatrixRow(inKey, mMatrix.size()); 372 mKeyMap.put(inKey, matrixRow); 373 mMatrix.add(matrixRow); 374 375 // Clear the cached size 376 mSize = null; 377 378 return matrixRow; 379 } 380 381 //-------------------------------------------------------------------------- 382 private int getMaxKeyLength() 383 { 384 int maxLength = 0; 385 for (K key : mKeyMap.keySet()) 386 { 387 if (key.toString().length() > maxLength) 388 { 389 maxLength = key.toString().length(); 390 } 391 } 392 393 return maxLength; 394 } 395 396 //-------------------------------------------------------------------------- 397 private MatrixCell<K, K, V> getCellWithSmallestValue(boolean inIncludeIdentityCells) 398 { 399 if (null == mSmallestValues 400 || mSmallestValuesIncludeIdentity != inIncludeIdentityCells) 401 { 402 mSmallestValues = new SortedList<>(sSmallestValuesCapacity); 403 mSmallestValuesIncludeIdentity = inIncludeIdentityCells; 404 } 405 406 if (0 == mSmallestValues.size()) 407 { 408 for (MatrixRow matrixRow : mMatrix) 409 { 410 int numCells = matrixRow.getData().size(); 411 for (int colIndex = 0; colIndex < numCells; colIndex++) 412 { 413 if (inIncludeIdentityCells 414 || matrixRow.getIndex() != colIndex) 415 { 416 V value = matrixRow.getData().get(colIndex); 417 if (value != null) 418 { 419 if (mSmallestValues.size() < sSmallestValuesRetentionSize 420 || value.compareTo(mSmallestValues.last().getValue()) <= 0) 421 { 422 K targetCol = mMatrix.get(colIndex).getKey(); 423 424 mSmallestValues.add(new MatrixCell<>(matrixRow.getKey(), targetCol, value)); 425 426 if (mSmallestValues.size() > sSmallestValuesCapacity - 2) 427 { 428 // Truncate the list 429 mSmallestValues.truncate(sSmallestValuesRetentionSize); 430 } 431 } 432 } 433 } 434 } 435 } 436 } 437 438// System.out.println(mSmallestValues.get(0).getValue() + " ( " + mSmallestValues.get(0).getRowKey() + " " + mSmallestValues.get(0).getColKey()); 439 return (CollectionUtil.hasValues(mSmallestValues) ? mSmallestValues.get(0) : null); 440 } 441 442 443 protected class MatrixRow implements Cloneable 444 { 445 private int mRowIndex; 446 private K mKey; 447 private List<V> mData; 448 449 //----------------------------------------------------------------------- 450 public MatrixRow(K inKey, int inIndex) 451 { 452 mKey = inKey; 453 mRowIndex = inIndex; 454 455 initializeData(); 456 } 457 458 //----------------------------------------------------------------------- 459 public void setKey(K inKey) 460 { 461 mKey = inKey; 462 } 463 464 //----------------------------------------------------------------------- 465 public K getKey() 466 { 467 return mKey; 468 } 469 470 //----------------------------------------------------------------------- 471 public void decrementIndex() 472 { 473 mRowIndex--; 474 } 475 476 //----------------------------------------------------------------------- 477 public int getIndex() 478 { 479 return mRowIndex; 480 } 481 482 //----------------------------------------------------------------------- 483 public void setData(List<V> inData) 484 { 485 mData = inData; 486 } 487 488 //----------------------------------------------------------------------- 489 public List<V> getData() 490 { 491 return mData; 492 } 493 494 //----------------------------------------------------------------------- 495 private void initializeData() 496 { 497 mData = new ArrayList<>(mRowIndex + 1); 498 for (int i = 0; i < mRowIndex + 1; i++) 499 { 500 mData.add(null); 501 } 502 } 503 504 //-------------------------------------------------------------------------- 505 @Override 506 public MatrixRow clone() 507 { 508 MatrixRow clone; 509 try 510 { 511 clone = (MatrixRow) super.clone(); 512 } 513 catch (CloneNotSupportedException e) 514 { 515 throw new ProgrammingException(e); 516 } 517 518 clone.mData = new ArrayList<>(mData); 519 520 return clone; 521 } 522 523 } 524 525}