开发者

How to implement `range()`

Intro

How to write public static range method or Range class, in Java 6, so that it would at least cover common functionality implemented in other programming languages?

If you want to answer the question, then you can just ignore following.


About

Time after time I miss functionality, that other languages have. I happen to have different coding style for about any language I write and I'm not willing to change that habit. So, if I want to reuse an algorithm, that I have written in a different language, then I have to make annoying little hacks or workarounds, to cover missing functionality. I'd like to find permanent and efficient fix for range().

For me - range() method returns range of input, possibly in a lazy manner and it has default values. At any point it has a start, a condition of ending and a way to get the next element. It should work perfectly in for each loop as well as outside it.

Notes

I would really prefer not to use a any external library, except Google Guava or equivalent. By equivalent I mean code that is properly tested, works excellently with JDK and is not considered deprecated.

How to implement `range()`

It might be unclear, so:

  • by How to write I mean - you do not have to write it, just describe common pitfalls and what are the pros and cons of the recommended approach.
  • this is loop code, but can also be used
  • this is not a homework :P

Aside from integer input, that would be the most used feature, I would really like it to work well with type instances of classes like BigInteger and Joda DateTime.

Ideas

  • must be able to use in for-each loop
  • generic, but typesafe
  • ables using pre-existing container methods, like: .cycle(), .filter(), .all(), .any(), transform()

As a method, range() header could look like:

/**TODO: Think about why / how to add step, limit, offset and cycle detection. */
publ开发者_Python百科ic static <T extends Comparable<T>> Iterable<T> //
range(T from, Predicate<T> to, Function<T, T> fNext) //
        throws NullPointerException, IllegalArgumentException {

As a personal preference, I have written Range as Builder pattern implementation.

Edit

Range implementations Scala, Python 3, Groovy, .Net (with linq) [c#, f#, vb and c++], ruby, PHP ... And all of them differ.

I might as well add an example of from what I want to do better ( simple sample case ).

public static <T extends Comparable<T>> Iterable<T> //
range(T from, Predicate<T> to, Function<T, T> fNext) //
        throws NullPointerException {
    Preconditions.checkNotNull(from);
    Preconditions.checkNotNull(to);
    Preconditions.checkNotNull(fNext);

    T current = from;
    ArrayList<T> result = Lists.newArrayList();
    if (to.apply(current)) result.add(current);
    while (to.apply(current = Preconditions.checkNotNull(fNext.apply(current))))
        result.add(current);
    return result;
}

or lazy alternative

//eats first element
public static <T extends Comparable<T>> Iterator<T> //
range2(final T from, final Predicate<T> to, final Function<T, T> fNext)
        throws NullPointerException, UnsupportedOperationException {
    Preconditions.checkNotNull(from);
    Preconditions.checkNotNull(to);
    Preconditions.checkNotNull(fNext);
    return new Iterator<T>() {
        T current = from;
        @Override public boolean hasNext() {return to.apply(current);}
        @Override public T next() {return current = Preconditions.checkNotNull(fNext.apply(current));}
        @Override public void remove() {throw new UnsupportedOperationException();}
    };
}


The pain point will probably be having to write the functions for each type:

  public static Function<Integer, Integer> intIncrementer(final int step) {
    class IntIncrementer implements Function<Integer, Integer> {
      private final int _step = step;

      @Override public Integer apply(Integer i) {
        return i + _step;
      }

      @Override public boolean equals(Object obj) {
        return (obj instanceof IntIncrementer) 
           && ((IntIncrementer) obj)._step == _step;
      }

      @Override public int hashCode() {
        return _step;
      }
    }

    return new IntIncrementer();
  }

Since there is no way to express i + _step in a generic fashion, you would have to reimplement this for every type you wanted to support (BigInteger, long, etc.)

I would question the need to use <T extends Comparable<T>> instead of just <T>. I see no advantage in imposing this restriction.

Unlike the collections, the Iterable type imposes no equality contract. It might be better to return a Range type that defined such a contract if you wanted to be able to compare ranges without externally iterating over all their elements.

public interface Range<T> extends Iterable<T> {
  // TODO: write the terms of the contract
  @Override public boolean equals(Object obj);
  @Override public int hashCode();
  // TODO: other useful methods?
}

When it comes to the primitives, there is going to be overhead in creating wrapper objects. This may be significantly less efficient over large ranges compared to just using a traditional incrementing for loop.


I would just take the closest path. Create an interface such as this:

interface Steppable<T>{
    T defaultStep(); //Returns 1 for most number types
    T value(); //Returns the value itself
    Steppable<T> step(T amount); //the stepping logic
}

Write a whole bunch of implementations of the interface for every type I want to use inside the range function (ints, longs, dates, etc.). Then create an overloaded range() function for each steppable type for ease of use.

I would go for this approach because it makes adding more steppable types fairly straight-forward. As for the disadvantage, the approach is somewhat verbose.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜