Last active
February 15, 2016 16:43
-
-
Save bnorm/ee8456a62dfb8d2856c9 to your computer and use it in GitHub Desktop.
Object ranges in Java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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