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}