package jo.sm.data; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class RLEBooleanArray implements Iterable<Integer> { private List<int[]> mCells; public RLEBooleanArray() { mCells = new ArrayList<>(); } public Iterator<Integer> iterator() { return new RLEBooleanArrayIterator(); } public boolean isEmpty() { return mCells.size() == 0; } public boolean get(int idx) { CellHit hit = find(idx); if (hit.mTargetFound) return true; else return false; } public void set(int idx, boolean val) { CellHit hit = find(idx); if (hit.mTargetFound) { if (!val) { setFalse(idx, hit); } } else { if (val) { setTrue(idx, hit); } } } private void setTrue(int idx, CellHit hit) { if (hit.mLowCellIdx < 0) if (hit.mHighCellIdx < 0) { // neither high nor low, therefore empty mCells.add(new int[] { idx, idx }); } else { // high but no low if (hit.mHighCell[0] - 1 == idx) { // merge mCells.remove(hit.mHighCellIdx); mCells.add(hit.mHighCellIdx, new int[] { idx, hit.mHighCell[1] }); } else { // insert mCells.add(hit.mHighCellIdx, new int[] { idx, idx }); } } else if (hit.mHighCellIdx < 0) { // low but no high if (hit.mLowCell[1] + 1 == idx) { // merge mCells.remove(hit.mLowCellIdx); mCells.add(hit.mLowCellIdx, new int[] { hit.mLowCell[0], idx }); } else { // insert mCells.add(hit.mLowCellIdx + 1, new int[] { idx, idx }); } } else { // high and low if (hit.mLowCell[1] + 1 == idx) { // adjacent to low if (hit.mHighCell[0] - 1 == idx) { // adjacent to high // merge high and low mCells.remove(hit.mHighCellIdx); mCells.remove(hit.mLowCellIdx); mCells.add(hit.mLowCellIdx, new int[] { hit.mLowCell[0], hit.mHighCell[1] }); } else { // not adjacent to high // merge into low mCells.remove(hit.mLowCellIdx); mCells.add(hit.mLowCellIdx, new int[] { hit.mLowCell[0], idx }); } } else { // not adjacent to low if (hit.mHighCell[0] - 1 == idx) { // adjacent to high // merge into high mCells.remove(hit.mHighCellIdx); mCells.add(hit.mHighCellIdx, new int[] { idx, hit.mHighCell[1] }); } else { // not adjacent to high // create new mCells.add(hit.mHighCellIdx, new int[] { idx, idx }); } } } } private void setFalse(int idx, CellHit hit) { if (hit.mTargetCell[0] == idx) { // is low bound if (hit.mTargetCell[1] == idx) { // is high bound // remove mCells.remove(hit.mTargetCellIdx); } else { // isn't high bound // shrink up mCells.remove(hit.mTargetCellIdx); mCells.add(hit.mTargetCellIdx, new int[] { hit.mTargetCell[0]+1, hit.mTargetCell[1]}); } } else { // isn't low bound if (hit.mTargetCell[1] == idx) { // is high bound // shrink down mCells.remove(hit.mTargetCellIdx); mCells.add(hit.mTargetCellIdx, new int[] { hit.mTargetCell[0], hit.mTargetCell[1]-1}); } else { // isn't high bound // split mCells.remove(hit.mTargetCellIdx); mCells.add(hit.mTargetCellIdx, new int[] { hit.mTargetCell[0], idx-1}); mCells.add(hit.mTargetCellIdx+1, new int[] { idx+1, hit.mTargetCell[1]}); } } } private CellHit find(int targetValue) { CellHit hit = new CellHit(); if (setupInitial(hit, targetValue)) return hit; for (;;) { if (hit.mLowCellIdx + 1 == hit.mHighCellIdx) { hit.mTargetFound = false; return hit; } int midCellIdx = (hit.mLowCellIdx + hit.mHighCellIdx)/2; int[] midCell = mCells.get(midCellIdx); if (targetValue < midCell[0]) { hit.mHighCellIdx = midCellIdx; hit.mHighCell = midCell; } else if (targetValue > midCell[1]) { hit.mLowCellIdx = midCellIdx; hit.mLowCell = midCell; } else { hit.mTargetCellIdx = midCellIdx; hit.mTargetCell = midCell; hit.mTargetFound = true; return hit; } } } private boolean setupInitial(CellHit hit, int targetValue) { hit.mTargetValue = targetValue; if (mCells.size() == 0) { hit.mTargetFound = false; hit.mLowCellIdx = -1; hit.mHighCellIdx = -1; return true; } hit.mLowCellIdx = 0; hit.mLowCell = mCells.get(hit.mLowCellIdx); if (hit.mLowCell[0] > targetValue) { // target is lower than low hit.mTargetFound = false; hit.mHighCellIdx = hit.mLowCellIdx; hit.mHighCell = hit.mLowCell; hit.mLowCellIdx = -1; hit.mLowCell = null; return true; } if (hit.mLowCell[1] >= targetValue) { // target is in low range hit.mTargetFound = true; hit.mTargetCellIdx = hit.mLowCellIdx; hit.mTargetCell = hit.mLowCell; hit.mLowCellIdx = -1; hit.mLowCell = null; return true; } hit.mHighCellIdx = mCells.size() - 1; hit.mHighCell = mCells.get(hit.mHighCellIdx); if (hit.mHighCell[1] < targetValue) { // target is higher than high hit.mTargetFound = false; hit.mLowCellIdx = hit.mHighCellIdx; hit.mLowCell = hit.mHighCell; hit.mHighCellIdx = -1; hit.mHighCell = null; return true; } if (hit.mLowCell[0] <= targetValue) { // target is in high range hit.mTargetFound = true; hit.mTargetCellIdx = hit.mHighCellIdx; hit.mTargetCell = hit.mHighCell; hit.mHighCellIdx = -1; hit.mHighCell = null; return true; } return false; } class CellHit { public int mTargetValue; public boolean mTargetFound; public int mTargetCellIdx; public int mLowCellIdx; public int mHighCellIdx; public int[] mTargetCell; public int[] mLowCell; public int[] mHighCell; } class RLEBooleanArrayIterator implements Iterator<Integer> { private int mAtValue; private int mAtCell; private int mHighValue; public RLEBooleanArrayIterator() { if (mCells.size() == 0) { mAtValue = 0; mAtCell = -1; mHighValue = -1; } else { mAtCell = 0; mAtValue = mCells.get(mAtCell)[0]; mHighValue = mCells.get(mCells.size() - 1)[1]; } } @Override public boolean hasNext() { return mAtValue <= mHighValue; } @Override public Integer next() { int ret = mAtValue; mAtValue++; if (mAtValue <= mHighValue) { int[] atCell = mCells.get(mAtCell); if (mAtValue > atCell[1]) { mAtCell++; atCell = mCells.get(mAtCell); mAtValue = atCell[0]; } } return ret; } @Override public void remove() { throw new IllegalStateException("Not implemented"); } } }