欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

A data matrix implementation 1 博客分类: OO* J#SwingUPBlog 

程序员文章站 2024-03-07 11:55:33
...
Though, as I wrote before, it's very hard to come up a universal dynamic data structure to fit all needs, sometimes we still need a somewhat general dynamic data structure due to requirements. For example, the spreadsheet could have as many columns during runtime, we can't determine the number of columns during compile time.

I have a very bad example of an implementation at work, it has some 3000 lines of code. I think it's for me to think about how it ends up like this and what are the better alternatives. Another motivation is still from the blog, I want to see how close I can get to ultimate - universal dynamic data structure, though I don't believe we can reach there. But the old saying says, you can't reach the North Star, but it always points to the right direction.

So here is my journey.

It's a data matrix, it has headers that we call fields, it has rows and columns. Each column has same data type.  This class is going across wires as well. And it has 3000 lines, for God's sake.

  • stripped out the code for memory allocation, replace them with Collection classes
  • stripped out the type meta data, removed all the type-safe getters/setters, they should be on the caller side, the meta data should be in the headers
  • stripped out the methods related to cell/row/column computations. This should be a dedicated class later on since the same kind of logic spreads in a few places.
  • stripped out the toXml() and other output methods.

After consolidate method signatures, The new class is like this

java 代码
 
  1. import java.util.List;  
  2. import java.util.Map;  
  3. import java.util.ArrayList;  
  4. import java.util.HashMap;  
  5. /** 
  6.  * A Data matrix class, mainly for weak typed data, e.g., a table in swing. 
  7.  * Implementation reference, TableModel, DefaultTableModel. 
  8.  * 
  9.  * Note that the header type Field is really used as a flag, except the 
  10.  * hashcode used in the Map class. 
  11.  */  
  12. public class DataMatrix  
  13. {  
  14.     private List columns; // column values  
  15.     private List fields;  // field values, order is significant.  
  16.   
  17.     private Map fieldToColumns; // what to do with duplicated fields.  
  18.   
  19.     public DataMatrix()  
  20.     {  
  21.         fields = new ArrayList();  
  22.         columns = new ArrayList();  
  23.         fieldToColumns = new HashMap();  
  24.     }  
  25.   
  26.     public DataMatrix(List fields)  
  27.     {  
  28.         this.fields = new ArrayList(fields);  
  29.         columns = new ArrayList(fields.size());  
  30.         fieldToColumns = new HashMap(fields.size());  
  31.     }  
  32.   
  33.     public DataMatrix(DataMatrix dataMatrix)  
  34.     {  
  35.         this.columns = new ArrayList(dataMatrix.columns);  
  36.         this.fields = new ArrayList(dataMatrix.fields);  
  37.         this.fieldToColumns = new HashMap(dataMatrix.fieldToColumns);  
  38.     }  
  39.   
  40.     //-----------------------------------------------------------------------  
  41.     // field operations  
  42.     //-----------------------------------------------------------------------  
  43.     public List getColumnFields() { return fields; }  
  44.   
  45.     public void setColumnFields(List fields) { this.fields = new ArrayList(fields); }  
  46.   
  47.     public Field getField(int col) { return (Field)fields.get(col); }  
  48.   
  49.     public int getColumnNumber(Field field)  
  50.     {  
  51.         Integer index = (Integer)fieldToColumns.get(field);  
  52.         // should throw a cumstomized exception  
  53.         if (index == nullthrow new IllegalArgumentException("field not found: " + field.toString());  
  54.   
  55.         return index.intValue();  
  56.     }  
  57.   
  58.     //-----------------------------------------------------------------------  
  59.     // column operations  
  60.     //-----------------------------------------------------------------------  
  61.     public int getNumberOfColumns() { return columns.size(); }  
  62.   
  63.     public List getColumn(int col) { return (List) columns.get(col); }  
  64.   
  65.     public List getColumn(Field key)  
  66.     {  
  67.         return (List) columns.get(getColumnNumber(key));  
  68.     }  
  69.   
  70.     public void addColumn(Field field, List column)  
  71.     {  
  72.         checkNullAndEqualSize(field, column);  
  73.   
  74.         fieldToColumns.put(field, new Integer(fields.size()));  
  75.         fields.add(field);  
  76.         columns.add(new ArrayList(column));  
  77.     }  
  78.   
  79.     public void insertColumn(int position, Field field, List column)  
  80.     {  
  81.         checkNullAndEqualSize(field, column);  
  82.   
  83.         fieldToColumns.put(field, new Integer(position));  
  84.         fields.add(position, field);  
  85.         columns.add(position, column);  
  86.     }  
  87.   
  88.     private void checkNullAndEqualSize(Field field, List column)  
  89.     {  
  90.         if (field == nullthrow new IllegalArgumentException("field is null");  
  91.   
  92.         if (column == nullthrow new IllegalArgumentException("column is null");  
  93.   
  94.         if (columns.size() > 0)  
  95.         {  
  96.             List existingRow = (List)columns.get(0);  
  97.             if (existingRow.size() != column.size())  
  98.             {  
  99.                 throw new IllegalArgumentException("column size doesn't match: "  
  100.                         + " existing column size=" + existingRow.size()  
  101.                         + " to be added column size=" + column.size());  
  102.             }  
  103.         }  
  104.     }  
  105.   
  106.     public void deleteColumn(int position)  
  107.     {  
  108.         fieldToColumns.remove(fields.get(position));  
  109.         fields.remove(position);  
  110.         columns.remove(position);  
  111.     }  
  112.   
  113.     public void deleteColumn(Field field)  
  114.     {  
  115.         Integer index = (Integer)fieldToColumns.get(field);  
  116.         if (index == nullreturn// no such field here, do nothing  
  117.   
  118.         fieldToColumns.remove(field);  
  119.         fields.remove(index.intValue());  
  120.         columns.remove(index.intValue());  
  121.     }  
  122.   
  123.     // reorder the data matrix with the given fields.  
  124.     // if there is extra fields, throws exception  
  125.     public void reorder(Field[] fieldsInNewOrder)  
  126.     {  
  127.         // check fields to see whether they are the same.  
  128.   
  129.         List newFields = new ArrayList();  
  130.         List newColumns = new ArrayList();  
  131.         Map newFieldToColumns = new HashMap();  
  132.         for (int i=0; i
  133.         {  
  134.             newFields.add(fieldsInNewOrder[i]);  
  135.             int index = ((Integer)fieldToColumns.get(fieldsInNewOrder[i])).intValue();  
  136.             newColumns.add(columns.get(index));  
  137.             newFieldToColumns.put(fieldsInNewOrder, new Integer(i));  
  138.         }  
  139.   
  140.         this.fields = newFields;  
  141.         this.columns = newColumns;  
  142.         this.fieldToColumns = newFieldToColumns;  
  143.     }  
  144.   
  145.     //-----------------------------------------------------------------------  
  146.     // row operations  
  147.     //-----------------------------------------------------------------------  
  148.     public int getNumberOfRows()  
  149.     {  
  150.         if (columns.size() == 0return 0;  
  151.   
  152.         List firstColumn = (List)columns.get(0);  
  153.         return firstColumn.size();    // should optimize this - trade speed with space.  
  154.     }  
  155.   
  156.     public List getRow(int row)  
  157.     {  
  158.         List ret = new ArrayList();  
  159.         for (int i=0; i
  160.         {  
  161.             List columnList = (List) columns.get(i);  
  162.             ret.add(columnList.get(row));  
  163.         }  
  164.   
  165.         return ret;  
  166.     }  
  167.   
  168.     public void addRow(List row)  
  169.     {  
  170.         checkSizeMatch(row);  
  171.   
  172.         for (int i=0; i
  173.         {  
  174.             List columnList = getExistingOrNewColumn(i);  
  175.             columnList.add(row.get(i));  
  176.         }  
  177.     }  
  178.   
  179.     public void insertRow(int position, List row)  
  180.     {  
  181.         checkSizeMatch(row);  
  182.   
  183.         for (int i=0; i
  184.         {  
  185.             List columnList = getExistingOrNewColumn(i);  
  186.             columnList.add(position, row.get(i));  
  187.         }  
  188.     }  
  189.   
  190.     private void checkSizeMatch(List row)  
  191.     {  
  192.         if (row == null)  
  193.         {  
  194.             throw new IllegalArgumentException("row is null");  
  195.         }  
  196.   
  197.         if (fields.size() == 0)  
  198.         {  
  199.             throw new IllegalStateException("set the fields before adding rows");  
  200.         }  
  201.   
  202.         // not empty and not equals  
  203.         if (columns.size() != 0 && row.size() != columns.size())  
  204.         {  
  205.             throw new IllegalArgumentException("mismatched column size: " +  
  206.                     "datamatrix num of columns=" + columns.size() + " to be "  
  207.                     + "added row size=" + row.size());  
  208.         }  
  209.     }  
  210.   
  211.     private List getExistingOrNewColumn(int i)  
  212.     {  
  213.         List columnList;  
  214.         if (i >= columns.size()) // empty columns  
  215.         {  
  216.             columnList = new ArrayList();  
  217.             columns.add(columnList);  
  218.         }  
  219.         else // existing  
  220.         {  
  221.             columnList = (List) columns.get(i);  
  222.         }  
  223.         return columnList;  
  224.     }  
  225.   
  226.     // normal, in a data matrix, we need to loop from n-1 to 0  
  227.     // in order to use this in a loop.  
  228.     public void deleteRow(int rowNumber)  
  229.     {  
  230.         for (int i=0; i
  231.         {  
  232.             List columnList = (List) columns.get(i);  
  233.             columnList.remove(rowNumber);  
  234.         }  
  235.     }  
  236.   
  237.     //-----------------------------------------------------------------------  
  238.     // element operations  
  239.     //-----------------------------------------------------------------------  
  240.     // before you call these two methods, make sure you do populate the data.  
  241.     // Otherwise you will get an IndexOutOfBoundsException.  
  242.     public void setCellValueAt(int row, int col, Object value)  
  243.     {  
  244.         if (col >= columns.size())  
  245.         {  
  246.             throw new IllegalArgumentException("try to set value at an uninitialized cell");  
  247.         }  
  248.   
  249.         ((List)columns.get(col)).set(row, value);  
  250.     }  
  251.   
  252.     public Object getCellValue(int row, int col)  
  253.     {  
  254.         if (col >= columns.size())  
  255.         {  
  256.             throw new IllegalArgumentException("try to get value at an uninitialized cell");  
  257.         }  
  258.   
  259.         List column = (List)columns.get(col);  
  260.         if (column == null || row >= column.size())  
  261.         {  
  262.             throw new IllegalArgumentException("try to get value at an uninitialized cell");  
  263.         }  
  264.         return ((List)columns.get(col)).get(row);  
  265.     }  
  266.   
  267.     //-----------------------------------------------------------------------  
  268.     // matrix operations  
  269.     //-----------------------------------------------------------------------  
  270.     // append rows at the bottom  
  271.     public void concatenateRows(DataMatrix dataMatrix)  
  272.     {  
  273.         // check same number of columns first.  
  274.         if (columns.size() != dataMatrix.getNumberOfColumns())  
  275.         {  
  276.             throw new IllegalArgumentException("Two matrices have different "  
  277.                + "number of columns: " + columns.size() + " != " +  
  278.                dataMatrix.getNumberOfColumns());  
  279.         }  
  280.   
  281.         for (int j=0; j
  282.         {  
  283.             ((List)columns.get(j)).addAll(dataMatrix.getColumn(j));  
  284.         }  
  285.     }  
  286.   
  287.     // append columns at the right end  
  288.     public void concatenateColumns(DataMatrix dataMatrix)  
  289.     {  
  290.         // check both matrices have the same number of rows.  
  291.         if (this.getNumberOfRows() != dataMatrix.getNumberOfRows())  
  292.         {  
  293.             throw new IllegalArgumentException("Two matrices have different "  
  294.                + "number of rows: " + getNumberOfRows() + " != " +  
  295.                dataMatrix.getNumberOfRows());  
  296.         }  
  297.   
  298.         int startCol = columns.size();  
  299.         for (int j=0; j
  300.         {  
  301.             fieldToColumns.put(dataMatrix.getField(j), new Integer(j + startCol));  
  302.             fields.add(dataMatrix.getField(j));  
  303.             columns.add(dataMatrix.getColumn(j));  
  304.         }  
  305.     }  
  306.   
  307.     // split the datamatrix to two which are the two sides of this field.  
  308.     // The specified field could be on the right/left/not included.  
  309.     public DataMatrix[] splitFrom(Field field)  
  310.     {  
  311.         int colNum = getColumnNumber(field);  
  312.   
  313.         DataMatrix leftMatrix = new DataMatrix();  
  314.         DataMatrix rightMatrix = new DataMatrix();  
  315.   
  316.         for (int i=0; i
  317.         {  
  318.             leftMatrix.addColumn((Field)fields.get(i), (List)columns.get(i));  
  319.         }  
  320.   
  321.         for (int i=colNum; i
  322.         {  
  323.             rightMatrix.addColumn((Field)fields.get(i), (List)columns.get(i));  
  324.         }  
  325.         return new DataMatrix[] {leftMatrix, rightMatrix};  
  326.     }  
  327.   
  328.     public String toString()  
  329.     {  
  330.         String ret = "";  
  331.   
  332.         if (fields == nullreturn ret;  
  333.   
  334.         // output fields first  
  335.         for (int i=0; i
  336.         {  
  337.             ret += fields.get(i).toString() + " | ";  
  338.         }  
  339.         ret += "\n";  
  340.   
  341.         if (columns == nullreturn ret;  
  342.   
  343.         // output rows  
  344.         for (int i=0; i
  345.         {  
  346.             for (int j=0; j
  347.             {  
  348.                 ret += ((List)columns.get(j)).get(i).toString() + " | ";  
  349.             }  
  350.             ret += "\n";  
  351.         }  
  352.   
  353.         return ret;  
  354.  }  

I am not surprised to see that the only dependency on the headers is just an empty interface, as a flag.

java 代码
 
  1. public interface Field  
  2. {  
  3. }  

If you compare the Swing's TableModel implementations to this one, it's almost the same, except the event handling portion, plus we have several extra methods. This is a good sign that we abstract the responsibility at the right level, not too many responsibilities.

The only relevant missing piece is the serialization. We can either implements Serializable or Externalizable.

In this implementation, I delegate the dynamic memory allocation to Collection classes. I could use a Map that maps fields to columns, but I instead use two Lists for speeding.

I also supplied a toString() method for debugging purpose, but left out other formatters, like toXml(), toCvs() etc.

Since this is more of a design issue, I left out most of the code untested. For simple testing, I created a simple field class:

java 代码
 
  1. public class StringField implements Field  
  2. {  
  3.     private String name;  
  4.   
  5.     public StringField(String name)  
  6.     {  
  7.         this.name = name;  
  8.     }  
  9.       
  10.     public String getName() { return name; }  
  11.   
  12.     public boolean equals(Object obj)  
  13.     {  
  14.         if (!(obj instanceof StringField)) return false;  
  15.   
  16.         StringField field = (StringField)obj;  
  17.         return this.name.equals(field.name);  
  18.     }  
  19.   
  20.     public int hashCode() { return name.hashCode(); }  
  21.   
  22.     public String toString() { return getName(); }  
  23. }  

A real world Field class would have dependencies on date, currency and other factors.