All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.chocosolver.util.objects.setDataStructures.iterable.IntIterableRangeSet Maven / Gradle / Ivy

There is a newer version: 4.10.17
Show newest version
/*
 * This file is part of choco-solver, http://choco-solver.org/
 *
 * Copyright (c) 2019, IMT Atlantique. All rights reserved.
 *
 * Licensed under the BSD 4-clause license.
 *
 * See LICENSE file in the project root for full license information.
 */
package org.chocosolver.util.objects.setDataStructures.iterable;


import org.chocosolver.solver.exception.SolverException;
import org.chocosolver.util.objects.setDataStructures.ISetIterator;
import org.chocosolver.util.objects.setDataStructures.SetType;

import java.util.Arrays;
import java.util.function.IntConsumer;

/**
 * Concrete implementation of {@link IntIterableSet} wherein values are stored in range set.
 * A range is made of two ints, the lower bound and the upper bound of the range.
 * A range can be a singleton, in that case, the lb and the ub are equal.
 * If the upper bound of range A is equal to lower bound of range B, then the two ranges can be merged into a single one.
 * 

* Project: choco. * * @author Charles Prud'homme * @since 14/01/2016. */ public class IntIterableRangeSet implements IntIterableSet { //*********************************************************************************** // VARIABLES //*********************************************************************************** public static final int MIN = Integer.MAX_VALUE / -2; public static final int MAX = Integer.MAX_VALUE / 2; /** * Store elements */ protected int[] ELEMENTS; /** * Used size in {@link #ELEMENTS}. * To get the number of range simply divide by 2. */ protected int SIZE; /** * Total number of elements in the set */ protected int CARDINALITY; /** Create an ISet iterator */ private ISetIterator iter; /** * Every public method must preserve these invariants. */ private void checkInvariants() { assert SIZE <= ELEMENTS.length; assert (SIZE & 1) == 0; // is even assert CARDINALITY >= 0; } //*********************************************************************************** // CONSTRUCTOR //*********************************************************************************** /** * Create an interval-based ordered set */ public IntIterableRangeSet() { ELEMENTS = new int[10]; SIZE = 0; CARDINALITY = 0; } /** * Create an interval-based ordered set initialized to [a,b] * * @param a lower bound of the interval * @param b upper bound of the interval */ public IntIterableRangeSet(int a, int b) { if (a > b) { throw new IndexOutOfBoundsException("Incorrect bounds [" + a + "," + b + "]"); } ELEMENTS = new int[10]; SIZE = 2; CARDINALITY = Math.addExact(b + 1, -a); ELEMENTS[0] = a; ELEMENTS[1] = b; } /** * Create an interval-based ordered set initialized to singleton {e} * @param e singleton value */ public IntIterableRangeSet(int e) { ELEMENTS = new int[10]; SIZE = 2; CARDINALITY = 1; ELEMENTS[0] = ELEMENTS[1] = e; } /** * Create an interval-based ordered set initialized to an array of values * @param values some values */ public IntIterableRangeSet(int[] values) { this(); addAll(values); } //*********************************************************************************** // METHODS //*********************************************************************************** @Override public String toString() { StringBuilder st = new StringBuilder(); if (SIZE == 0) { st.append('\u2205'); } else { for (int i = 0; i < SIZE - 1; i += 2) { if (i == 0 && ELEMENTS[i] == MIN) { st.append('(').append("-∞"); } else { st.append('[').append(ELEMENTS[i]); } st.append(','); if (ELEMENTS[i + 1] == MAX) { st.append("+∞").append(")∪"); } else { st.append(ELEMENTS[i + 1]).append("]∪"); } } st.deleteCharAt(st.length() - 1); } return st.toString(); } public String toSmartString() { StringBuilder st = new StringBuilder(); st.append("{"); for (int i = 0; i < SIZE - 1; i += 2) { if (ELEMENTS[i] == ELEMENTS[i + 1]) { st.append(ELEMENTS[i]).append(','); } else { st.append(ELEMENTS[i]).append("..").append(ELEMENTS[i + 1]).append(','); } } if (SIZE > 0) st.deleteCharAt(st.length() - 1); st.append("}"); return st.toString(); } /** * @return number of ranges in this */ public int getNbRanges() { return SIZE >> 1; } public int cardinality() { return CARDINALITY; } public int minOfRange(int r) { return ELEMENTS[r << 1]; } public int maxOfRange(int r) { return ELEMENTS[(r << 1) + 1]; } @Override public int min() { if (isEmpty()) throw new IllegalStateException("cannot find minimum of an empty set"); return ELEMENTS[0]; } @Override public int max() { if (isEmpty()) throw new IllegalStateException("cannot find maximum of an empty set"); return ELEMENTS[SIZE - 1]; } @Override public boolean add(int e) { boolean modified = false; int p = rangeOf(e); // if e is not in a range if (p < 0) { grow(SIZE + 2); int i = (-p - 1) << 1; //if (i > 0) { int c = i > 0 && ELEMENTS[i - 1] + 1 == e ? 1 : 0; c += i < SIZE && e == ELEMENTS[i] - 1 ? 2 : 0; switch (c) { case 0: // insert a new range System.arraycopy(ELEMENTS, i, ELEMENTS, i + 2, SIZE - i); ELEMENTS[i] = ELEMENTS[i + 1] = e; SIZE += 2; break; case 1: // e is the new lower bound assert ELEMENTS[i - 1] + 1 == e; ELEMENTS[i - 1] = e; break; case 2: // e is the new upper bound assert ELEMENTS[i] - 1 == e; ELEMENTS[i] = e; break; case 3: // merge two ranges System.arraycopy(ELEMENTS, i + 1, ELEMENTS, i - 1, SIZE - i); SIZE -= 2; break; default: throw new SolverException("Unexpected mask " + c); } modified = true; CARDINALITY++; } return modified; } @Override public boolean addAll(int... values) { int c = CARDINALITY; for (int i = 0; i < values.length; i++) { add(values[i]); } return CARDINALITY - c > 0; } @Override public boolean addAll(IntIterableSet set) { if (set.isEmpty()) return false; int c = CARDINALITY; if (!set.isEmpty()) { int v = set.min(); while (v < Integer.MAX_VALUE) { add(v); v = set.nextValue(v); } } return CARDINALITY > c; } public boolean addAll(IntIterableRangeSet set) { int c = CARDINALITY; if (!set.isEmpty()) { int s2 = set.SIZE >> 1; if (s2 > 0) { int j = 0; do { addBetween(set.ELEMENTS[j << 1], set.ELEMENTS[(j << 1) + 1]); j++; } while (j < s2); } } return CARDINALITY < c; } @Override public boolean retainAll(IntIterableSet set) { int c = CARDINALITY; if (set.isEmpty()) { this.clear(); } else { int last = max(); for (int i = min(); i <= last; i = nextValue(i)) { if (!set.contains(i)) { remove(i); } } } return c - CARDINALITY > 0; } public boolean retainAll(IntIterableRangeSet set) { int c = CARDINALITY; if (set.isEmpty()) { this.clear(); return c - CARDINALITY > 0; } else { return IntIterableSetUtils.intersectionOf(this, set); } } @Override public boolean remove(int e) { boolean modified = false; int p = rangeOf(e); // if e is not in a range if (p >= 0) { int i = (p - 1) << 1; int c = ELEMENTS[i] == e ? 1 : 0; c += ELEMENTS[i + 1] == e ? 2 : 0; switch (c) { case 0: // split range in two ranges grow(SIZE + 2); System.arraycopy(ELEMENTS, i + 1, ELEMENTS, i + 3, SIZE - i - 1); ELEMENTS[i + 1] = e - 1; ELEMENTS[i + 2] = e + 1; SIZE += 2; break; case 1: // increase lower of the range ELEMENTS[i]++; break; case 2: // decrease upper of the range ELEMENTS[i + 1]--; break; case 3: // delete a range System.arraycopy(ELEMENTS, i + 2, ELEMENTS, i, SIZE - i - 2); SIZE -= 2; break; default: throw new SolverException("Unexpected mask " + c); } modified = true; CARDINALITY--; } return modified; } @Override public boolean removeAll(IntIterableSet set) { int c = CARDINALITY; if (!set.isEmpty()) { int v = set.min(); while (v < Integer.MAX_VALUE) { remove(v); v = set.nextValue(v); } } return CARDINALITY < c; } public boolean removeAll(IntIterableRangeSet set) { int c = CARDINALITY; if (!set.isEmpty()) { int s2 = set.SIZE >> 1; if (s2 > 0) { int j = 0; do { removeBetween(set.ELEMENTS[j << 1], set.ELEMENTS[(j << 1) + 1]); j++; } while (j < s2); } } return CARDINALITY < c; } @Override public void clear() { CARDINALITY = 0; SIZE = 0; } @Override public SetType getSetType() { return SetType.RANGESET; } public boolean addBetween(int a, int b) { if (a > b) { throw new IndexOutOfBoundsException("Incorrect bounds [" + a + "," + b + "]"); } boolean change; int s1 = SIZE >> 1; int s2 = 1; // since a <= b if (s1 > 0) { int i = 0, j = 0; int s = 0, c = 0; int[] e = new int[SIZE]; int lbi, ubi, lbj, ubj, lb, ub; lb = lbi = ELEMENTS[0]; ub = ubi = ELEMENTS[1]; lbj = a; ubj = b; if (lb > lbj) { lb = lbj; ub = ubj; } boolean extend; // TODO: replace while loop with rangeOf while (i < s1 || j < s2) { extend = false; if (lb - 1 <= lbi && lbi <= ub + 1) { ub = Math.max(ub, ubi); extend = i < s1; if (++i < s1) { lbi = ELEMENTS[i << 1]; ubi = ELEMENTS[(i << 1) + 1]; } } if (lb - 1 <= lbj && lbj <= ub + 1) { ub = Math.max(ub, ubj); extend |= j < s2; j++; } if (!extend) { if (s + 2 > e.length) { // overflow-conscious code int oldCapacity = e.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity < s + 2) newCapacity = s + 2; // minCapacity is usually close to size, so this is a win: e = Arrays.copyOf(e, newCapacity); } e[s++] = lb; e[s++] = ub; c += ub - lb + 1; if (i < s1) { lb = lbi; ub = ubi; if (j < s2 && lbi > lbj) { lb = lbj; ub = ubj; } } else if (j < s2) { lb = lbj; ub = ubj; } } } if (s + 2 > e.length) { // overflow-conscious code int oldCapacity = e.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity < s + 2) newCapacity = s + 2; // minCapacity is usually close to size, so this is a win: e = Arrays.copyOf(e, newCapacity); } e[s++] = lb; e[s++] = ub; c += ub - lb + 1; ELEMENTS = e; SIZE = s; change = (CARDINALITY != c); CARDINALITY = c; } else { grow(1); ELEMENTS[0] = a; ELEMENTS[1] = b; SIZE = 2; CARDINALITY = Math.addExact(b + 1, -a); change = true; } return change; } @Override public boolean removeBetween(int f, int t) { boolean rem = false; if (f > t) { throw new IllegalArgumentException("Cannot remove from empty range [" + f + "," + t + "]"); } int rf = rangeOf(f); if (rf < 0) { // find closest after rf *= -1; if (rf > SIZE >> 1) { return false; } f = ELEMENTS[(rf - 1) << 1]; } assert rf > 0; int rt = rangeOf(t, (rf - 1) << 1, SIZE); if (rt < 0) { // find closest range before rt = -rt - 1; if (rt < 1) { return false; } t = ELEMENTS[((rt - 1) << 1) + 1]; } assert rt > 0; int i = (rf - 1) << 1; int j = (rt - 1) << 1; if (rf <= rt) { int dcard = -(f - ELEMENTS[i] + ELEMENTS[j + 1] - t); dcard += ELEMENTS[i + 1] - ELEMENTS[i] + 1; if (rf < rt) { for (int k = i + 2; k <= j + 1; k += 2) { dcard += ELEMENTS[k + 1] - ELEMENTS[k] + 1; } // remove useless range System.arraycopy(ELEMENTS, j + 1, ELEMENTS, i + 1, SIZE - (j + 1)); SIZE -= (rt - rf) << 1; } CARDINALITY -= dcard; int c = ELEMENTS[i] == f ? 1 : 0; c += ELEMENTS[i + 1] == t ? 2 : 0; switch (c) { case 0: // split the range into two ranges grow(SIZE + 2); System.arraycopy(ELEMENTS, i, ELEMENTS, i + 2, SIZE - i); ELEMENTS[i + 1] = f - 1; ELEMENTS[i + 2] = t + 1; SIZE += 2; break; case 1: // update the lower bound of the range ELEMENTS[i] = t + 1; break; case 2: // update the upper bound of the range ELEMENTS[i + 1] = f - 1; break; case 3: // remove the range if (i < SIZE - 2) { System.arraycopy(ELEMENTS, i + 2, ELEMENTS, i, SIZE - (i + 2)); } SIZE -= 2; break; } rem = true; } return rem; } public boolean retainBetween(int f, int t) { if (f > t) { throw new IllegalArgumentException("Cannot retain from empty range [" + f + "," + t + "]"); } int rf = rangeOf(f); if (rf < 0) { // find closest after rf *= -1; if (rf << 1 > SIZE) { this.clear(); return true; } f = ELEMENTS[(rf - 1) << 1]; } assert rf > 0; int rt = rangeOf(t, (rf - 1) << 1, SIZE); if (rt < 0) { // find closest range before rt = -rt - 1; if (rt < 1) { this.clear(); return true; } t = ELEMENTS[((rt - 1) << 1) + 1]; } assert rt > 0; int i = (rf - 1) << 1; int j = (rt - 1) << 1; if (rf <= rt) { ELEMENTS[i] = f; ELEMENTS[j + 1] = t; System.arraycopy(ELEMENTS, i, ELEMENTS, 0, j - i + 2); SIZE = (rt - rf + 1) << 1; CARDINALITY = 0; for (int k = 0; k < SIZE; k += 2) { CARDINALITY += ELEMENTS[k + 1] - ELEMENTS[k] + 1; } } else { this.clear(); } return true; } @Override public int nextValue(int e) { e++; int p = rangeOf(e); int next = Integer.MAX_VALUE; if (p == -1 && SIZE > 0) { next = ELEMENTS[0]; } else if (p >= 0) { next = e; } else if (p > -((SIZE >> 1) + 1)) { return ELEMENTS[(-p - 1) << 1]; } return next; } @Override public int nextValueOut(int e) { e++; int p = rangeOf(e); int next; if (p >= 0) { next = ELEMENTS[((p - 1) << 1) + 1] + 1; } else { next = e; } return next; } @Override public int previousValue(int e) { e--; int p = rangeOf(e); int prev = Integer.MIN_VALUE; if (p == -((SIZE >> 1) + 1) && SIZE > 0) { prev = ELEMENTS[SIZE - 1]; } else if (p >= 0) { prev = e; } else if (p < -1) { prev = ELEMENTS[((-p - 1) << 1) - 1]; } return prev; } @Override public int previousValueOut(int e) { e--; int p = rangeOf(e); int prev; if (p >= 0) { prev = ELEMENTS[((p - 1) << 1)] - 1; } else { prev = e; } return prev; } @Override public boolean contains(int o) { if (CARDINALITY == 1) { return ELEMENTS[0] == o; } return rangeOf(o) >= 0; } @Override public IntIterableRangeSet duplicate() { IntIterableRangeSet ir = new IntIterableRangeSet(); ir.ELEMENTS = this.ELEMENTS.clone(); ir.CARDINALITY = this.CARDINALITY; ir.SIZE = this.SIZE; checkInvariants(); return ir; } public IntIterableRangeSet copyFrom(IntIterableRangeSet me) { this.clear(); for (int s = 0; s < me.SIZE; s += 2) { pushRange(me.ELEMENTS[s], me.ELEMENTS[s + 1]); } checkInvariants(); return this; } @Override public int size() { return CARDINALITY; } @Override public ISetIterator newIterator() { return new ISetIterator() { private boolean started = false; private int current; @Override public void reset() { started = false; } @Override public boolean hasNext() { if (started) { return nextValue(current) < Integer.MAX_VALUE; } else { return !isEmpty(); } } @Override public int nextInt() { if (started) { current = nextValue(current); } else { started = true; current = min(); } return current; } }; } /** * add the value x to all integers stored in this set * * @param x value to add */ public void plus(int x) { for (int i = 0; i < SIZE; i++) { ELEMENTS[i] += x; } } @Override public ISetIterator iterator() { if (iter == null) { iter = newIterator(); } iter.reset(); return iter; } /** * subtract the value x to all integers stored in this set * * @param x value to add */ public void minus(int x) { for (int i = 0; i < SIZE; i++) { ELEMENTS[i] -= x; } } /** * multiply by x to all integers stored in this set * * @param x value to add */ public void times(int x) { if (x > 0) { for (int i = 0; i < SIZE; i++) { ELEMENTS[i] *= x; } CARDINALITY *= x; } else if (x < 0) { for (int i = 0; i < (SIZE >> 1); i++) { int t = ELEMENTS[i]; ELEMENTS[i] = ELEMENTS[SIZE - i - 1] * x; ELEMENTS[SIZE - i - 1] = t * x; } CARDINALITY *= -x; } else { this.clear(); this.add(0); } } /** * By convention, range are numbered starting from 1 (not 0). * * @param x a value * @return the range index if the value is in the set or -range point - 1 otherwise * where range point corresponds to the range directly greater than the key */ public int rangeOf(int x) { return rangeOf(x, 0, SIZE); } /** * By convention, range are numbered starting from 1 (not 0). * * @param x a value * @return the range index if the value is in the set or -range point - 1 otherwise * where range point corresponds to the range directly greater than the key */ protected int rangeOf(int x, int fromIndex, int toIndex) { if (toIndex - fromIndex < 15) { int q = -((toIndex >> 1) + 1); for (int r = fromIndex; r < toIndex - 1; r += 2) { if (x < ELEMENTS[r]) { q = -((r >> 1) + 1); break; } else if (x <= ELEMENTS[r + 1]) { q = (r >> 1) + 1; break; } } return q; } int p = Arrays.binarySearch(ELEMENTS, fromIndex, toIndex, x); // if pos is positive, the value is a bound of a range if (p >= 0) { p >>= 1; } else if (p == -1) { p--; } else if (p == -(toIndex + 1)) { p = -((toIndex >> 1) + 2);// -2 because add 1 as last instruction } else { // is x in a range or not p = -(p + 1); p >>= 1; if (!(ELEMENTS[p << 1] < x && x < ELEMENTS[(p << 1) + 1])) { p = -(p + 2); // -2 because add 1 as last instruction } } return p + 1; } /** * Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */ void grow(int minCapacity) { if (minCapacity - ELEMENTS.length > 0) { // overflow-conscious code int oldCapacity = ELEMENTS.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; // minCapacity is usually close to size, so this is a win: ELEMENTS = Arrays.copyOf(ELEMENTS, newCapacity); } } /** * Push a range at the end of this set * @param lb lower bound of the range * @param ub upper bound of the range */ void pushRange(int lb, int ub) { assert SIZE == 0 || ELEMENTS[SIZE - 1] < lb - 1; assert lb <= ub; grow(SIZE + 2); ELEMENTS[SIZE++] = lb; ELEMENTS[SIZE++] = ub; CARDINALITY += Math.addExact(ub + 1, -lb); } /** * Compact the array in memory */ public void compact() { ELEMENTS = Arrays.copyOf(ELEMENTS, SIZE); } /** * Turn this into the complement of this. * calling : *

set.flip().flip()
* goes back to the original set. * @return this turned into its complement, based on {@link #MIN} and {@link #MAX} */ public IntIterableRangeSet flip() { return flip(MIN, MAX); } /** * Turn this into the complement of this. * calling : *
set.flip().flip()
* goes back to the original set. * @return this turned into its complement, based on lb, ub */ public IntIterableRangeSet flip(int lb, int ub) { if (SIZE == 0) { // empty set pushRange(lb, ub); } else if (ELEMENTS[0] <= lb && ELEMENTS[1] >= ub) { // all clear(); } else { boolean smin = ELEMENTS[0] <= lb; boolean emax = ELEMENTS[SIZE - 1] >= ub; if (!smin && !emax) { grow(SIZE + 2); SIZE += 2; } if (smin && emax) { SIZE -= 2; }// else no need to change the length of ELEMENTS // two cases: CARDINALITY = 0; if (smin) { int i = 1; int max = emax ? SIZE : SIZE - 2; while (i < max) { ELEMENTS[i - 1] = ELEMENTS[i++] + 1; ELEMENTS[i - 1] = ELEMENTS[i++] - 1; CARDINALITY += ELEMENTS[i - 2] - ELEMENTS[i - 3] + 1; } if (!emax) { ELEMENTS[i - 1] = ELEMENTS[i++] + 1; ELEMENTS[i - 1] = ub; CARDINALITY += ELEMENTS[i - 1] - ELEMENTS[i - 2] + 1; } } else { int i = SIZE - 1; if (!emax) { ELEMENTS[i--] = ub; } else { ELEMENTS[i--] = ELEMENTS[i] - 1; } while (i > 0) { ELEMENTS[i] = ELEMENTS[--i] + 1; try { CARDINALITY += ELEMENTS[i + 2] - ELEMENTS[i + 1] + 1; } catch (ArrayIndexOutOfBoundsException e) { System.out.print("tt"); } ELEMENTS[i] = ELEMENTS[--i] - 1; } ELEMENTS[i] = lb; CARDINALITY += ELEMENTS[i + 1] - ELEMENTS[i] + 1; } } return this; } /** * Apply the operation c on each value in this set * @param c an operation */ public void forEachValueIn(IntConsumer c) { for (int s = 0; s < SIZE; s += 2) { for (int i = ELEMENTS[s]; i <= ELEMENTS[s + 1]; i++) { c.accept(i); } } } /** * Apply the operation c on each value in : ]{@link #min()}, {@link #max()}[ \ this set. * @param c an operation */ public void forEachValueOut(IntConsumer c) { for (int s = 1; s < SIZE - 1; s += 2) { for (int i = ELEMENTS[s] + 1; i < ELEMENTS[s + 1]; i++) { c.accept(i); } } } /** * @return an array containing all of the elements in this set in * sorted sequence */ public int[] toArray() { int[] a = new int[CARDINALITY]; int k = 0; for (int i = 0; i < SIZE - 1; i += 2) { if (ELEMENTS[i] == ELEMENTS[i + 1]) { a[k++] = ELEMENTS[i]; } else { for (int j = ELEMENTS[i]; j <= ELEMENTS[i + 1]; j++) { a[k++] = j; } } } return a; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy