/* * Copyright (c) 2016, Metron, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Metron, Inc. nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL METRON, INC. BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.metsci.glimpse.util.primitives.rangeset; import com.metsci.glimpse.util.primitives.sorted.SortedInts; import com.metsci.glimpse.util.primitives.sorted.SortedIntsArray; public class IntRangeSetModifiable implements IntRangeSet { // The coalesce() method is more efficient when it doesn't // have to work in-place, so keep two arrays and swap them protected SortedIntsArray ranges; protected SortedIntsArray scratch; public IntRangeSetModifiable( ) { this.ranges = new SortedIntsArray( ); this.scratch = new SortedIntsArray( ); } @Override public boolean contains( int x ) { return ( this.ranges.indexAtOrBefore( x ) % 2 == 0 ); } @Override public SortedInts ranges( ) { return this.ranges; } public void add( int first, int count ) { if ( count > 0 ) { int start = first; int iBeforeStart = this.ranges.indexAtOrBefore( start ); boolean startsInExistingRange = ( iBeforeStart % 2 == 0 ); if ( !startsInExistingRange && iBeforeStart >= 0 && start == this.ranges.v( iBeforeStart ) ) { // Broaden to overlap an adjacent existing range start--; iBeforeStart--; startsInExistingRange = true; } int end = first + count; int iAfterEnd = this.ranges.indexAtOrAfter( end ); boolean endsInExistingRange = ( iAfterEnd % 2 == 1 ); if ( !endsInExistingRange && iAfterEnd < this.ranges.n && end == this.ranges.v( iAfterEnd ) ) { // Broaden to overlap an adjacent existing range end++; iAfterEnd++; endsInExistingRange = true; } if ( startsInExistingRange && endsInExistingRange ) { this.ranges.removeRange( iBeforeStart + 1, iAfterEnd ); } else if ( startsInExistingRange ) { this.ranges.a[ iBeforeStart + 1 ] = end; this.ranges.removeRange( iBeforeStart + 2, iAfterEnd ); } else if ( endsInExistingRange ) { this.ranges.a[ iAfterEnd - 1 ] = start; this.ranges.removeRange( iBeforeStart + 1, iAfterEnd - 1 ); } else { this.ranges.removeRange( iBeforeStart + 1, iAfterEnd ); this.ranges.prepForInsert( iBeforeStart + 1, 2 ); this.ranges.a[ iBeforeStart + 1 ] = start; this.ranges.a[ iBeforeStart + 2 ] = end; } } } public void coalesce( int tolerance ) { if ( tolerance > 0 && this.ranges.n >= 4 ) { // Reuse swapped out array, to avoid allocating a new one SortedIntsArray coalesced = this.scratch; coalesced.clear( ); // Begin a coalesced range, but don't finish it until we see // whether the ranges that follow it can be coalesced with it coalesced.append( this.ranges.v( 0 ) ); int pendingEnd = this.ranges.v( 1 ); for ( int i = 2; i < this.ranges.n; i += 2 ) { // If the next range is too far away to coalesce, finish // the current coalesced range and begin a new one int start = this.ranges.v( i + 0 ); if ( pendingEnd + tolerance < start ) { coalesced.append( pendingEnd ); coalesced.append( start ); } pendingEnd = this.ranges.v( i + 1 ); } // Finish the final coalesced range coalesced.append( pendingEnd ); // Swap arrays, to avoid allocating a new one on the next coalesce this.scratch = this.ranges; this.ranges = coalesced; } } public void clear( ) { this.ranges.clear( ); } @Override public String toString( ) { StringBuilder s = new StringBuilder( ); s.append( "{ " ); for ( int i = 0; i < this.ranges.n; i += 2 ) { int start = this.ranges.v( i + 0 ); int end = this.ranges.v( i + 1 ); s.append( "[" ).append( start ).append( "," ).append( end ).append( ") " ); } s.append( "}" ); return s.toString( ); } }