/******************************************************************************* * * Copyright 2010 Alexandru Craciun, and individual contributors as indicated * by the @authors tag. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 3 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. ******************************************************************************/ package org.netxilia.api.impl.utils; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.netxilia.api.impl.event.DispatchableEvent; import org.netxilia.api.impl.event.DispatchableEventSupport; import org.netxilia.api.impl.utils.BlockEvent.EventType; import org.netxilia.api.impl.utils.intervals.Interval; import org.netxilia.api.impl.utils.intervals.IntervalTree; import com.google.common.util.concurrent.MoreExecutors; public class OrderedBlockMatrix<V> implements ISparseMatrix<V> { private int blockCount = 0; private final IntervalTree<Map<OrderedBlock, BlockMetadata<V>>> columns; // private final IntervalTree<Collection<BlockMetadata<V>>> columns; private final OrderedBlockEventSupport eventSupport; public OrderedBlockMatrix() { this.columns = new IntervalTree<Map<OrderedBlock, BlockMetadata<V>>>(); // this.columns = new IntervalTree<Collection<BlockMetadata<V>>>(); eventSupport = new OrderedBlockEventSupport(); } /** * Set the value for the cell at the given position. <br> * <br> * * In order to reduce the number of different value instances, we try to aggressively merge the given value with * equal neighboring value. Therefore, it is possible that the resulting value is a different instance than the * given one. * */ @Override public void set(int firstRow, int firstCol, int lastRow, int lastCol, V value) { for (int r = firstRow; r <= lastRow; ++r) { for (int c = firstCol; c <= lastCol; ++c) { set(r, c, value); } } } /** * Set the value for the cell at the given position. <br> * <br> * * In order to reduce the number of different value instances, we try to aggressively merge the given value with * equal neighboring value. Therefore, it is possible that the resulting value is a different instance than the * given one. * * */ @Override public void set(int row, int col, V value) { set(new OrderedBlock(row, col), value); } private void set(OrderedBlock pos, V newMD) { // first, check if we have something on this position BlockMetadata<V> blockMD = getBlockMetadata(pos); if (blockMD == null) { // we don't. Insert it, attempting to merge with its neighbors attemptMerge(pos, newMD); return; } // we have. First check if we have the same value V oldMD = blockMD.getValue(); if (oldMD.equals(newMD)) { return; } // it doesn't. We have to remove and split the current block deleteBlockMetadata(blockMD.getBlock()); // insert the generated parts, attempting to merge them with their neighbors OrderedBlock block = blockMD.getBlock(); if (!pos.equals(block)) { for (OrderedBlock part : block.split(pos)) { attemptMerge(part, oldMD); } } // finally, insert the position, attempting to merge it with its neighbors attemptMerge(pos, newMD); } /** * This inserts the given value for the specified cells block attempting to merge it with block's neighbors. The * merging is performed recursively, i.e. we try to merge the result of a successful merge. * * @return the final value block resulted after insertion. */ private BlockMetadata<V> attemptMerge(OrderedBlock block, V md) { // no need to insert this value in the tree if (md == null) { return null; } BlockMetadata<V> newBMD = null; // for each of the neighboring cells of this block for (OrderedBlock pos : block.neighborCells()) { BlockMetadata<V> neighBMD = getBlockMetadata(pos); // if we have a real neighboring block if (neighBMD != null) { OrderedBlock neighBlock = neighBMD.getBlock(); // check if we can merge with it, i.e. has the same side size and the same value if (neighBlock.canMerge(block) && neighBMD.getValue().equals(md)) { // it does! we remove the previous block and create the merged block deleteBlockMetadata(neighBlock); OrderedBlock newBlock = neighBlock.merge(block); V newMD = neighBMD.getValue(); // keep existing value // attempt to merge further the resulting block newBMD = attemptMerge(newBlock, newMD); // stop the merging here. All other sides were already checked recursively return newBMD; } } } // didn't merge with any neighbor. Create and record the new block newBMD = new BlockMetadata<V>(block, md); insertBlockMetadata(newBMD); return newBMD; } /** Record the given value block. Tracks changes for persistence. */ private void insertBlockMetadata(BlockMetadata<V> newBMD) { restoreBlockMetadata(newBMD); eventSupport.fireInsertedEvent(newBMD); } private void addBlock(BlockMetadata<V> md) { Interval interval = new Interval(md.getBlock().getFirstCol(), md.getBlock().getLastCol()); Map<OrderedBlock, BlockMetadata<V>> blocks = columns.get(interval); if (blocks == null) { blocks = new HashMap<OrderedBlock, BlockMetadata<V>>(); columns.put(interval, blocks); } blocks.put(md.getBlock(), md); columns.recalculate(); } private BlockMetadata<V> deleteBlock(OrderedBlock block) { Interval interval = new Interval(block.getFirstCol(), block.getLastCol()); Map<OrderedBlock, BlockMetadata<V>> blocks = columns.get(interval); if (blocks == null) { return null; } BlockMetadata<V> deleted = blocks.remove(block); if (blocks.isEmpty()) { columns.remove(interval); } if (deleted != null) { columns.recalculate(); } return deleted; } /** * Restore from the storage system a block of value, i.e. load it in memory. * * @throws IllegalArgumentException * if the given block overlaps with an existing one. * */ public void restoreBlockMetadata(BlockMetadata<V> newBMD) { addBlock(newBMD); // addBlock(columns, new Interval(newBMD.getBlock().getFirstCol(), newBMD.getBlock().getLastCol()), newBMD); blockCount++; } /** Delete the value for the block at the given position. Tracks changes for persistence. */ private void deleteBlockMetadata(OrderedBlock block) { // take it first time it is found in an interval BlockMetadata<V> deletedBMD = deleteBlock(block); if (deletedBMD == null) { return; } blockCount--; eventSupport.fireDeletedEvent(deletedBMD); } private BlockMetadata<V> getBlockMetadata(OrderedBlock block) { Interval interval = new Interval(block.getFirstCol(), block.getLastCol()); List<Map.Entry<Interval, Map<OrderedBlock, BlockMetadata<V>>>> entries = columns.searchEntries(interval); // for each interval look into the corresponding blocks to find the one containing the provided block. // TODO could use an interval tree for column search also for (Map.Entry<Interval, Map<OrderedBlock, BlockMetadata<V>>> entry : entries) { for (BlockMetadata<V> storedBlock : entry.getValue().values()) { if (storedBlock.getBlock().contains(block)) { return storedBlock; } } } return null; } /** * Enlarge vertically the blocks that overlap the given row and shift down by one position the ones below. * * @return the list of affected entries */ public List<? extends ISparseMatrixEntry<V>> insertRow(final int row, final InsertMode insertMode) { return shiftBlocks(row, 0, new IBlockTransformer<V>() { @Override public List<BlockMetadata<V>> apply(BlockMetadata<V> orig) { return orig.withInsertedRow(row, insertMode); } }); } /** * Shrink vertically the blocks that overlap the given row, delete the ones that don't exist anymore and shift up by * one position the ones below. * * @return the list of affected entries */ public List<? extends ISparseMatrixEntry<V>> deleteRow(final int row) { return shiftBlocks(row, 0, new IBlockTransformer<V>() { @Override public List<BlockMetadata<V>> apply(BlockMetadata<V> orig) { return orig.withDeletedRow(row); } }); } /** * Enlarge horizontally the blocks that overlap the given column and shift right by one position the ones to the * right. * * @return the list of affected entries */ public List<? extends ISparseMatrixEntry<V>> insertColumn(final int col, final InsertMode insertMode) { return shiftBlocks(0, col, new IBlockTransformer<V>() { @Override public List<BlockMetadata<V>> apply(BlockMetadata<V> orig) { return orig.withInsertedCol(col, insertMode); } }); } /** * Shrink horizontally the blocks that overlap the given column, delete the ones that don't exist anymore and shift * left by one position the ones to the right. * * @return the list of affected entries */ public List<? extends ISparseMatrixEntry<V>> deleteColumn(final int col) { return shiftBlocks(0, col, new IBlockTransformer<V>() { @Override public List<BlockMetadata<V>> apply(BlockMetadata<V> orig) { return orig.withDeletedCol(col); } }); } /** * Defines a transformation over a value block, to be used mainly for shifting blocks on rows and columns insertions * and deletions. */ private interface IBlockTransformer<VV> { public List<BlockMetadata<VV>> apply(BlockMetadata<VV> orig); } /** Helper for insert/delete Row/Col methods */ private List<? extends ISparseMatrixEntry<V>> shiftBlocks(int minRow, int minCol, IBlockTransformer<V> transformer) { // remove the affected blocks for shifting List<BlockMetadata<V>> shiftedBMList = new LinkedList<BlockMetadata<V>>(); List<BlockMetadata<V>> deletedBMList = new LinkedList<BlockMetadata<V>>(); for (Map<OrderedBlock, BlockMetadata<V>> blocks : columns.values()) { for (BlockMetadata<V> bm : blocks.values()) { if (bm.getBlock().getLastRow() < minRow || bm.getBlock().getLastCol() < minCol) { continue; } deletedBMList.add(bm); List<BlockMetadata<V>> newBlocks = transformer.apply(bm); // TODO completely deleted cells (newBlocks == null) should somehow be returned - better return a pair // old, // new if (newBlocks != null) { shiftedBMList.addAll(newBlocks); } } } // first delete the modified blocks for (BlockMetadata<V> shBM : deletedBMList) { deleteBlockMetadata(shBM.getBlock()); } // re-insert the shifted blocks for (BlockMetadata<V> shBM : shiftedBMList) { insertBlockMetadata(shBM); } return shiftedBMList; } /** Get the value for the cell at the given position. */ @Override public V get(int row, int col) { // OrderedBlock searchedBlock = new OrderedBlock(row, col); // for (Map.Entry<OrderedBlock, BlockMetadata<V>> entry : mdBlocks.entrySet()) { // if (entry.getKey().contains(searchedBlock)) { // return entry.getValue().getValue(); // } // } // return null; // TODO the algorithm should be fixed because it does not work in all the cases BlockMetadata<V> md = getBlockMetadata(new OrderedBlock(row, col)); return md != null ? md.getValue() : null; } @Override public int getBlockCount() { return blockCount; } @Override public Collection<? extends ISparseMatrixEntry<V>> entries() { List<BlockMetadata<V>> entries = new ArrayList<BlockMetadata<V>>(getBlockCount()); for (Map<OrderedBlock, BlockMetadata<V>> blocks : columns.values()) { entries.addAll(blocks.values()); } return entries; } @Override public String toString() { StringBuilder s = new StringBuilder(); s.append("{#").append(getBlockCount()).append("\n"); for (ISparseMatrixEntry<V> entry : entries()) { s.append(entry).append("\n"); } s.append("}"); return s.toString(); } public String treeToString() { return "ROWS:" + columns; } private class OrderedBlockEventSupport extends DispatchableEventSupport<ISparseMatrixListener<V>> { public OrderedBlockEventSupport() { super(MoreExecutors.sameThreadExecutor()); } public void fireDeletedEvent(BlockMetadata<V> deletedBMD) { if (super.getListeners().hasNext()) { fireEvent(new BlockEvent<V>(deletedBMD, EventType.deleted)); } } public void fireInsertedEvent(BlockMetadata<V> newBMD) { if (super.getListeners().hasNext()) { fireEvent(new BlockEvent<V>(newBMD, EventType.inserted)); } } public void fireEvent(BlockEvent<V> ev) { fireEvent(new DispatchableEvent<ISparseMatrixListener<V>, BlockEvent<V>>(ev) { @Override public void dispatch(ISparseMatrixListener<V> target, BlockEvent<V> event) { if (event.getType() == EventType.deleted) { target.onDeletedBlock(event); } else if (event.getType() == EventType.inserted) { target.onInsertedBlock(event); } } }); } } @Override public void addEntryListener(ISparseMatrixListener<V> listener) { eventSupport.addListener(listener); } @Override public void removeEntryListener(ISparseMatrixListener<V> listener) { eventSupport.removeListener(listener); } public static void main(String[] args) { OrderedBlockMatrix<Integer> matrix = new OrderedBlockMatrix<Integer>(); long t1 = System.currentTimeMillis(); for (int r = 0; r < 100; ++r) { for (int c = 0; c < 100; ++c) { matrix.set(r, c, c % 2); } } // for (int r = 0; r < 4; ++r) { // for (int c = 0; c < 4; ++c) { // matrix.set(r, c, c % 2); // System.out.println(matrix); // System.out.println(matrix.columns); // System.out.println("---------------"); // } // } long t2 = System.currentTimeMillis(); System.out.println("time:" + (t2 - t1)); System.out.println("rows:" + matrix.columns.size()); System.out.println("blocks:" + matrix.getBlockCount()); System.out.println(matrix); } }