ints(), longs(), doubles() <was> Re: Ranges
Howard Lovatt
howard.lovatt at gmail.com
Wed May 8 01:38:47 PDT 2013
I haven't done this in my code, but you could generalize Range to objects
by introducing an Enumerable interface. Note 1: this forces the Range to be
closed not half open because an object past the end of the range may well
not exist. Note 2: an empty range has to be signalled with a null from
because a closed range that can step either direction needs some illegal
limit to signal empty. Enumerable would be:
// Both from and to inclusive. Implementing classes should use ordinal
for equals and hashCode.
interface Enumerable<T extends Enumerable<T>> extends Comparable<T> {
T from();
T to();
T enumeration(int ordinal) throws IllegalArgumentException;
int ordinal();
default T next() throws IllegalArgumentException { return
enumeration(ordinal() + 1); }
default T previous() throws IllegalArgumentException { return
enumeration(ordinal() - 1); }
default boolean hasNext() { return ordinal() < to().ordinal(); }
default boolean hasPrevious() { return ordinal() > from().ordinal(); }
default long numberTo(final T to) { return to.ordinal() -
(long)ordinal(); }
default Range<T> rangeTo(final T to) throws IllegalArgumentException {
final Range<T> range = new Range<>().from(this).to(to);
if (ordinal() > to.ordinal()) { range.step(-1); }
return range;
}
default int compareTo(final T other) throws ArithmeticException {
final int thisOrdinal = ordinal();
final int otherOrdinal = other.ordinal();
if (thisOrdinal == otherOrdinal) { return 0; }
if (thisOrdinal < otherOrdinal) { return -1; }
return 1;
}
}
Then have classes Enum, Char, Byte, Short, and Integer (not Long because in
has more values than can be represented by an int and ordinal in Enum
returns an int) implement Enumerable (e.g. an Enum of Months could now be
used to give a range of Months). This Enumerable interface would then let
you specify a Range for a reference type:
// Should implement Clonable! Ranges don't wrap. From and to both
inclusive. Null from means empty. Null to means infinite. Both from and to
null means empty.
class Range<T extends Enumerable<T>> {
private T from = null;
private T to = null;
private int step = 1;
private Predicate<T> toLambda = null;
private Function<T> stepLambda = null;
public Stream<T> stream() { ... }
public T from() { return from; }
public Range<T> from(final Range<T> from) { this.from = from; return
this; }
public T to() throws IllegalStateException {
if (toLambda != null) { throw IllegalStateException("Limit specified
by whileTrue"); }
return to;
}
public Range<T> to(final T to) {
toLambda = null;
this.to = to;
return this;
}
public int step() throws IllegalStateException {
if (toStep != null) { throw IllegalStateException("Step specified by
step(Function)"); }
return step;
}
public Range<T> step(final int step) {
toStep = null;
this.step = step;
return this;
}
public Range<T> whileTrue(final Predicate<T> whileTrue) { toLambda =
whileTrue; return this; }
public Range<T> step(final Function<T> step) { stepLambda = step;
return this; }
public Range<T> subRange(final T from, final T to) {
if (!contains(from)) { throw IllegalArgumentException("from not in
range"); }
if (!contains(to)) { throw IllegalArgumentException("to not in
range"); }
return from.rangeTo(to);
}
public long size() {
if (from == null) { return 0; } // empty
if (step == 0 || toLambda != null || stepLambda != null) { return
Long.MAX_VAL; } // unknown/infinite
final int toOrdinal;
if (to != null) { toOrdinal = to.ordinal(); }
else {
if (switch > 0) { toOrdinal = from.to().ordinal(); }
else { toOrdinal = from.from().ordinal(); }
}
final int sizeM1 = ((long)toOrdinal - from.ordinal()) / step;
if (sizeM1 < 0) { throw IllegalStateException("Negative size"); }
return sizeM1 + 1;
}
public boolean contains(final T other) {
if (from == null) { return false; }
if (toLambda != null || stepLambda != null) { return
stream().parallel().contains(other); }
switch (step) {
case -1:
if (from.compareTo(other) < 0) { return false; }
if (to == null) { return true; }
return to.compareTo(other) <= 0;
case 0:
return from.compareTo(other) == 0;
case 1:
if (from.compareTo(other) > 0) { return false; }
if (to == null) { return true; }
return to.compareTo(other) >= 0;
}
return stream().parallel().contains(other);
}
}
I think the concept of a general Range would be useful in Java, it is
certainly well used in other languages and I have made good use of a
primitive specialisation to int.
-- Howard.
On 7 May 2013 19:44, Stephen Colebourne <scolebourne at joda.org> wrote:
> Just wanted to note that ranges are one of the most commonly seen
> things not in the JDK. Many librraies have written versions of the
> concept, and related JVM languages often build them in.
>
> It also applies as a concept to date/time (JSR-310). We descoped
> ranges as they really should be considered in a broader JDK manner.
> That said, it would be good to get something in that would support
> ranges.
>
> (in date/time, ranges of Instant and LocalDate are probably the most
> common that I have seen, and both are Comparable. Thus, any Range
> implementation based on Comparable is likely to support date/time
> requirements).
>
> Stephen
>
>
>
> On 6 May 2013 14:08, Paul Sandoz <paul.sandoz at oracle.com> wrote:
> > Hi Howard,
> >
> > I like the concept of a Range type and builder, although it might be too
> late to introduce a Range class now (there is nothing stopping us from
> adding it later).
> >
> > In the Streams API the size of the range is important so that the range
> spliterator can produce balanced splits.
> >
> > Paul.
> >
> > On May 4, 2013, at 3:45 AM, Howard Lovatt <howard.lovatt at gmail.com>
> wrote:
> >
> >> I write numerical code and find something like:
> >>
> >> from(start).to(exclusiveEnd).step(step).stream()...
> >>
> >> with generalised to (whileTrue) and step variations, e.g.:
> >>
> >> from(start).whileTrue((i) -> i < limit).step((i) -> 2 * i).stream()...
> >>
> >> useful in my in house library, it's great for paralleling existing
> loops!
> >>
> >> I implement it with something like this (I am using an in-house stream
> library so below is para-phrasing what I do):
> >>
> >> public interface IntStream {
> >> ...
> >> class Range {
> >> private int from = 0;
> >> private IntPredicate whileTrue = (notUsed) -> true;
> >> private IntFunction step = (i) -> i + 1;
> >> public Range from(int from) { this.from = from; return this; }
> >> public Range to(int exclusiveEnd) { whileTrue = (i) -> i <
> exclusiveEnd; return this; }
> >> public Range step(int step) { this.step = (i) -> i + 1; return this;
> }
> >> public Range step(IntFunction step) { this.step = step; return this;
> }
> >> public Range whileTrue(IntPredicate whileTrue) { this.whileTrue =
> whileTrue; return this; }
> >> public IntStream stream() { ... }
> >> }
> >> static Range from(int from) { return new Range().from(from); }
> >> static Range to(int exclusiveEnd) { return new
> Range().to(exclusiveEnd); }
> >> static Range step(int step) { return new Range().step(step); }
> >> static Range step(IntFunction step) { return new Range().step(step); }
> >> static Range whileTrue(IntPredicate whileTrue) { return new
> Range().whileTrue(whileTrue); }
> >> static IntStream stream() { return new Range().stream(); }
> >> }
> >>
> >> -- Howard.
> >>
> >> Sent from my iPad
> >>
> >> On 04/05/2013, at 3:35 AM, Tim Peierls <tim at peierls.net> wrote:
> >>
> >>> On Fri, May 3, 2013 at 1:26 PM, Brian Goetz <brian.goetz at oracle.com>
> wrote:
> >>>
> >>>> I don't think it would. While "indexes" does capture the essence of a
> >>>>> common use of this method, I think of [start, bound) as a range, and
> >>>>> would look it up under that name. range and rangeClosed are very
> similar
> >>>>> concepts; they should have similar names. The potential for confusion
> >>>>> doesn't seem that significant to me.
> >>>>>
> >>>>
> >>>> With two forms, I agree. But the next two questions are:
> >>>> - what about decreasing ranges?
> >>>> - what about decreasing closed/half-open ranges (whichever the above
> >>>> isnt)?
> >>>>
> >>>> Now, if the answer to these incremental questions is "you lose", then
> >>>> range and rangeClosed are fine, and we're done.
> >>>>
> >>>
> >>> All of these variations seem like things that aren't hard to roll for
> >>> yourself, especially with a few good examples in the javadoc.
> >>>
> >>> But if range and rangeClosed really don't feel like enough to you, then
> >>> retain the stepped versions (with a different, uglier name) but add an
> >>> open/closed argument so it's only one extra method for each of the
> >>> primitive types and not two.
> >>>
> >>> rangeFarAfield(start, bound, step, HALF_OPEN);
> >>>
> >>> Ugly enough that people won't use it unless they have to.
> >>>
> >>> --tim
> >>
> >
>
>
--
-- Howard.
More information about the lambda-dev
mailing list