Skip to content

Instantly share code, notes, and snippets.

@bnorm
Last active February 15, 2016 16:43
Show Gist options
  • Save bnorm/ee8456a62dfb8d2856c9 to your computer and use it in GitHub Desktop.
Save bnorm/ee8456a62dfb8d2856c9 to your computer and use it in GitHub Desktop.
Object ranges in Java
import java.util.Comparator;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.function.IntPredicate;
public final class Range<T> {
private final Comparator<? super T> comparator;
private final Endpoint<T> min;
private final Endpoint<T> max;
// todo public?
private Range(Comparator<? super T> comparator,
Optional<? extends T> min,
boolean minOpen,
Optional<? extends T> max,
boolean maxOpen) {
super();
Objects.requireNonNull(comparator);
if (min.isPresent() && max.isPresent() && comparator.compare(min.get(), max.get()) > 0) {
throw new IllegalArgumentException("wrong order, min > max");
}
this.comparator = comparator;
this.min = minOpen ? Endpoint.minOpen(comparator, min) : Endpoint.minClosed(comparator, min);
this.max = maxOpen ? Endpoint.maxOpen(comparator, max) : Endpoint.maxClosed(comparator, max);
}
@Override
public String toString() {
return min + ", " + max;
}
public Endpoint<T> min() {
return min;
}
public Endpoint<T> max() {
return max;
}
public boolean contains(T value) {
return min.contains(value) && max.contains(value);
}
public boolean intersects(Range<? extends T> range) {
/**
* Case 1.) Range 1 contains only Range 2 minimum
* Range 1: A1 ---- B1
* Range 2: A2 ---- B2
*
* Case 2.) Range 1 contains only Range 2 maximum
* Range 1: A1 ---- B1
* Range 2: A2 ---- B2
*
* Case 3.) Range 1 contains all of Range 2
* Range 1: A1 --------- B1
* Range 2: A2 - B2
*
* Case 4.) Range 1 is completely contained within Range 2
* Range 1: A1 - B1
* Range 2: A2 --------- B2
*
* Case 5.) Range 1 is completely greater than Range 2
* Range 1: A1 - B1
* Range 2: A2 - B2
*
* Case 6.) Range 1 is completely less than Range 2
* Range 1: A1 - B1
* Range 2: A2 - B2
*
* In all cases BUT 6, B1 is greater than A2. In all cases BUT 5, A1 is less than B2. Taking the union of
* these 2 conditions results in the set {1..4} which is the condition we are trying to prove.
*/
return max.contains(range.min()) && min.contains(range.max());
}
public boolean contains(Range<? extends T> range) {
/** See case 3 in {@link #intersects(Range)} */
return min.contains(range.min()) && max.contains(range.max());
}
public boolean adjacent(Range<? extends T> range) {
if (max.isBound() && range.min().isBound() && comparator.compare(max.value(), range.min().value()) == 0) {
return true;
} else if (min.isBound() && range.max().isBound()
&& comparator.compare(min.value(), range.max().value()) == 0) {
return true;
} else {
return false;
}
}
//
public Optional<Range<T>> union(Range<? extends T> range) {
if (!intersects(range) && !adjacent(range)) {
return Optional.empty();
}
Endpoint<? extends T> minEndpoint = min.contains(range.min()) ? min : range.min();
Endpoint<? extends T> maxEndpoint = max.contains(range.max()) ? max : range.max();
return Optional.of(new Range<>(comparator,
Optional.ofNullable(minEndpoint.value),
minEndpoint.open,
Optional.ofNullable(maxEndpoint.value),
maxEndpoint.open));
}
public Optional<Range<T>> intesection(Range<? extends T> range) {
if (!intersects(range)) {
return Optional.empty();
}
Endpoint<? extends T> minEndpoint = min.contains(range.min()) ? range.min() : min;
Endpoint<? extends T> maxEndpoint = max.contains(range.max()) ? range.max() : max;
return Optional.of(new Range<>(comparator,
Optional.ofNullable(minEndpoint.value),
minEndpoint.open,
Optional.ofNullable(maxEndpoint.value),
maxEndpoint.open));
}
// Factory
// Bound
public static <T> Range<T> closed(Comparator<? super T> comparator, T min, T max) {
Objects.requireNonNull(min);
Objects.requireNonNull(max);
return new Range<T>(comparator, Optional.of(min), false, Optional.of(max), false);
}
public static <T extends Comparable<? super T>> Range<T> closed(T min, T max) {
return closed(Comparator.naturalOrder(), min, max);
}
public static <T> Range<T> open(Comparator<? super T> comparator, T min, T max) {
Objects.requireNonNull(min);
Objects.requireNonNull(max);
return new Range<T>(comparator, Optional.of(min), true, Optional.of(max), true);
}
public static <T extends Comparable<? super T>> Range<T> open(T min, T max) {
return open(Comparator.naturalOrder(), min, max);
}
public static <T> Range<T> openClosed(Comparator<? super T> comparator, T min, T max) {
Objects.requireNonNull(min);
Objects.requireNonNull(max);
return new Range<T>(comparator, Optional.of(min), true, Optional.of(max), false);
}
public static <T extends Comparable<? super T>> Range<T> openClosed(T min, T max) {
return openClosed(Comparator.naturalOrder(), min, max);
}
public static <T> Range<T> closedOpen(Comparator<? super T> comparator, T min, T max) {
Objects.requireNonNull(min);
Objects.requireNonNull(max);
return new Range<T>(comparator, Optional.of(min), false, Optional.of(max), true);
}
public static <T extends Comparable<? super T>> Range<T> closedOpen(T min, T max) {
return closedOpen(Comparator.naturalOrder(), min, max);
}
// Unbound
public static <T> Range<T> closedUnbound(Comparator<? super T> comparator, T min) {
Objects.requireNonNull(min);
return new Range<T>(comparator, Optional.of(min), false, Optional.empty(), true);
}
public static <T extends Comparable<? super T>> Range<T> closedUnbound(T min) {
return closedUnbound(Comparator.naturalOrder(), min);
}
public static <T> Range<T> openUnbound(Comparator<? super T> comparator, T min) {
Objects.requireNonNull(min);
return new Range<T>(comparator, Optional.of(min), true, Optional.empty(), true);
}
public static <T extends Comparable<? super T>> Range<T> openUnbound(T min) {
return openUnbound(Comparator.naturalOrder(), min);
}
public static <T> Range<T> unboundClosed(Comparator<? super T> comparator, T max) {
Objects.requireNonNull(max);
return new Range<T>(comparator, Optional.empty(), true, Optional.of(max), false);
}
public static <T extends Comparable<? super T>> Range<T> unboundClosed(T max) {
return unboundClosed(Comparator.naturalOrder(), max);
}
public static <T> Range<T> unboundOpen(Comparator<? super T> comparator, T max) {
Objects.requireNonNull(max);
return new Range<T>(comparator, Optional.empty(), true, Optional.of(max), true);
}
public static <T extends Comparable<? super T>> Range<T> unboundOpen(T max) {
return unboundOpen(Comparator.naturalOrder(), max);
}
public static <T> Range<T> unbound(Comparator<? super T> comparator) {
return new Range<T>(comparator, Optional.empty(), true, Optional.empty(), true);
}
public static <T extends Comparable<? super T>> Range<T> unbound() {
return unbound(Comparator.<T>naturalOrder());
}
// Endpoint
public static final class Endpoint<T> {
private final Comparator<? super T> comparator;
private final boolean min;
private final boolean open;
private final T value;
private final IntPredicate contains;
private Endpoint(Comparator<? super T> comparator, boolean min, Optional<? extends T> value, boolean open) {
super();
if (!value.isPresent() && !open) {
// todo or can they?
throw new IllegalArgumentException("Endpoint cannot be unbound and closed");
}
this.comparator = comparator;
this.min = min;
this.value = value.orElse(null);
this.open = open;
if (min) {
if (open) {
this.contains = compare -> compare > 0; // greater than
} else {
this.contains = compare -> compare >= 0; // greater than or equal
}
} else {
if (open) {
this.contains = compare -> compare < 0; // less than
} else {
this.contains = compare -> compare <= 0; // less than or equal
}
}
}
private static <T> Endpoint<T> minOpen(Comparator<? super T> comparator, Optional<? extends T> value) {
return new Endpoint<>(comparator, true, value, true);
}
private static <T> Endpoint<T> minClosed(Comparator<? super T> comparator, Optional<? extends T> value) {
return new Endpoint<>(comparator, true, value, false);
}
private static <T> Endpoint<T> maxOpen(Comparator<? super T> comparator, Optional<? extends T> value) {
return new Endpoint<>(comparator, false, value, true);
}
private static <T> Endpoint<T> maxClosed(Comparator<? super T> comparator, Optional<? extends T> value) {
return new Endpoint<>(comparator, false, value, false);
}
public boolean isOpen() {
return open;
}
public boolean isClosed() {
return !open;
}
public boolean isBound() {
return value != null;
}
public boolean isUnbound() {
return value == null;
}
public T value() {
if (value == null) {
throw new NoSuchElementException("Unbound endpoints do not have a value");
}
return value;
}
public boolean contains(T t) {
if (isUnbound()) {
// Unbound endpoints always contain the value
return true;
} else {
int compare = comparator.compare(t, value);
return contains.test(compare);
}
}
private boolean contains(Endpoint<? extends T> endpoint) {
if (isUnbound()) {
// Unbound endpoints always contain the endpoint
return true;
} else if (endpoint.isUnbound()) {
// This endpoint is not unbound and the other is - only contains if it is the opposite
return endpoint.min != min;
} else {
int compare = comparator.compare(endpoint.value(), value);
if (compare == 0 && endpoint.isOpen()) {
return false;
}
return contains.test(compare);
}
}
@Override
public String toString() {
String str;
if (min) {
str = value != null ? value.toString() : "-inf";
if (open) {
str = "(" + str;
} else {
str = "[" + str;
}
} else {
str = value != null ? value.toString() : "+inf";
if (open) {
str = str + ")";
} else {
str = str + "]";
}
}
return str;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment