google / guava

Google core libraries for Java
Apache License 2.0
50.18k stars 10.91k forks source link

RFE: Add support for Ranges #322

Closed gissuebot closed 10 years ago

gissuebot commented 10 years ago

Original issue created by j...@nwsnet.de on 2010-02-03 at 09:29 AM


I'd like to see a simple count(n) method in Iterables(and probably in Iterators, too). This would allow to easily generate a range of numbers in a memory-efficient way (i.e. without the need to keep the whole range in memory).

E.g., count(20) would generate the numbers 20, 21, 22, 23, and so on. A default starting value (I'd expect 0) is conceivable, though not really necessary.

If there is a much better way to achieve the above and which I might have missed, please enlighten me.

gissuebot commented 10 years ago

Original comment posted by finnw1 on 2010-02-03 at 09:37 AM


I would put it in Ints/Longs rather than Iterables/Iterators.

gissuebot commented 10 years ago

Original comment posted by j...@nwsnet.de on 2010-02-08 at 09:34 AM


Suppliers.count() (returning an instance of Supplier<Integer> and/or Supplier<Long>; maybe the latter would be sufficient?) might be a conceivable place for it, too.

gissuebot commented 10 years ago

Original comment posted by kevinb@google.com on 2010-02-12 at 05:08 PM


This is one of the things our forthcoming Range stuff will support. There may still be value in a "shortcut" method like this in Itera*s, but we'll see at the time, and it wouldn't be called "count" (since I expected that to mean "count the number of elements left in the iteration).

gissuebot commented 10 years ago

Original comment posted by j...@nwsnet.de on 2010-02-14 at 05:23 PM


As long as these ranges are Iterables (and are lazy) and support an "open end", I'll be fine.

gissuebot commented 10 years ago

Original comment posted by j...@nwsnet.de on 2010-08-18 at 01:08 PM


Any news regarding the promised Range stuff?

gissuebot commented 10 years ago

Original comment posted by boppenheim@google.com on 2010-12-30 at 01:21 AM


Issue #506 has been merged into this issue.

gissuebot commented 10 years ago

Original comment posted by boppenheim@google.com on 2010-12-30 at 01:22 AM


(No comment entered for this change.)

gissuebot commented 10 years ago

Original comment posted by fry@google.com on 2011-01-26 at 10:40 PM


(No comment entered for this change.)


Status: Accepted Labels: Type-Enhancement

gissuebot commented 10 years ago

Original comment posted by kevinb@google.com on 2011-01-27 at 06:16 AM


(No comment entered for this change.)


Status: Started

gissuebot commented 10 years ago

Original comment posted by kevinb@google.com on 2011-01-27 at 06:17 AM


Issue #429 has been merged into this issue.

gissuebot commented 10 years ago

Original comment posted by j...@nwsnet.de on 2011-03-31 at 12:06 PM


Any news regarding this? Having ranges would also allow simplifying or even replacing some of those ugly for loops.

gissuebot commented 10 years ago

Original comment posted by kevinb@google.com on 2011-04-08 at 02:06 AM


(No comment entered for this change.)


Labels: Milestone-Release10

gissuebot commented 10 years ago

Original comment posted by kevinb@google.com on 2011-05-03 at 10:48 PM


(No comment entered for this change.)


Status: Fixed

gissuebot commented 10 years ago

Original comment posted by j...@nwsnet.de on 2011-05-05 at 12:46 PM


How is this class meant to be used? I see neither a public constructor nor a public factory method nor classes like Ints implement methods that would yield a range.

Oh, and is apply(C) going to be deprecated forever and right from the start because the name inherited from Predicate is ambiguous for ranges?

gissuebot commented 10 years ago

Original comment posted by cgdecker on 2011-05-05 at 02:16 PM


You use the Ranges class to create Ranges. And yes, I imagine apply(C) is going to be deprecated forever because you should always use the more descriptive contains(C) when calling a method directly. Only code treating the Range as a Predicate should be using apply.

gissuebot commented 10 years ago

Original comment posted by kevinb@google.com on 2011-05-05 at 02:31 PM


We treat the meaning of @Deprecated as: "there is no good reason that your code should invoke this method (directly)." It's a common misconception that deprecation is only about "I'm going to delete this." Ordering.from(Ordering) is another example of a deprecated method we expect to have forever.

gissuebot commented 10 years ago

Original comment posted by j...@nwsnet.de on 2011-05-06 at 09:09 AM


Oh, I totally forgot about Ranges while taking a deeper look into Ranges and its Javadocs.

But I still can't imagine how this can be used to solve my initially (though maybe not sufficiently explicitly) described use case of iterating over ranges of numbers. I'd expect something like for (Integer x : Ranges.closed(1, 100)) to work, as a cleaner and less error-prone alternative to incrementing an int in a for loop, and similar to Scala's 1 to 100 or Python's range(1, 101), for example.

Ranges.atLeast(20) resembles my above count(20) example, but I still need to be able to iterate over it for as long as I decide not to break the loop over it (which, as I'd expect, has to be ultimately done with open ranges anyway).

What did I miss this time? I guess it doesn't implement Iterable or something like that for a reason, including the above one about not terminating by itself in case of (half-)open ranges.

gissuebot commented 10 years ago

Original comment posted by ogregoire on 2011-05-06 at 11:56 AM


Indeed, that is somehow unexpected.

It looks like the automated generation of numbers isn't supported by Range, which is the main reason why I starred the bug: a python-like range().

Current implementation of Range is interesting but much less than the initial request that yo...@nwsnet.de initially asked.

gissuebot commented 10 years ago

Original comment posted by neveue on 2011-05-06 at 12:17 PM


For this, you need to combine a Range with a DiscreteDomain:

for (int i : Ranges.closed(1, 100).asSet(DiscreteDomains.integers()))

gissuebot commented 10 years ago

Original comment posted by raymond.rishty on 2011-05-06 at 01:14 PM


@nev..., I believe the initial request was that there would be an iterator that would generate the next number on-the-fly ("in a memory-efficient way"), rather than "keep the whole range in memory".

Personally, I don't think this would be a particularly complicated thing to implement yourself, and I don't know how much it would really add for general use (though that's not my call).

gissuebot commented 10 years ago

Original comment posted by cgdecker on 2011-05-06 at 01:44 PM


@raymond: The ImmutableSet returned by asSet(DiscreteDomain) (note that it isn't called "toSet") does generate numbers on the fly in a memory-efficient manner.

gissuebot commented 10 years ago

Original comment posted by raymond.rishty on 2011-05-06 at 05:29 PM


My mistake--very well then :)

gissuebot commented 10 years ago

Original comment posted by kevinb@google.com on 2011-05-06 at 11:11 PM


As you can tell from

for (int i : Ranges.closed(1, 100).asSet(DiscreteDomains.integers()))

it is clearly not a design goal of Ranges to support this use case cleanly. Why? Because even if we did the best we could to support it, it would still be better to do this:

for (int i = 1; i <= 100; i++)

gissuebot commented 10 years ago

Original comment posted by ogregoire on 2011-05-07 at 06:28 PM


Well, the for loop is not the only use case. Personally, I won't use that much. I really wanted Ranges to implement Iterable in order to easily create data structure of numbers that follow a simple rule, transform the lists, filter them, etc.

Looks like I'll be stuck with the ugly asSet(DiscreteDomain.integers()), which vocabulary is absolutely correct, but not understandable for the common of developers, especially non-native English speakers.

gissuebot commented 10 years ago

Original comment posted by j...@nwsnet.de on 2011-05-09 at 10:15 AM


Hm, bummer.

The discrete domain stuff is far too complicated for being used commonly. Also, while asSet is returning an immutable set with a fixed iteration order, it is irritating as a reader would expect a range to return some kind of list or another iterable that is not looking as it wouldn't care about ordering.

My issue with counting for loops is they are hard to grok in detail at a quick glance and thus easily hide off-by-one errors. ("Modern") "scripting" languages, e.g. Ruby, became really popular for a reason; clean ways to create ranges is one tiny part of the story.

I'd happily add some chars to the for one-liner in order to gain clarity.

A counting iterator indeed is easy to implement, but it still could be used in lots of places even in smaller projects. And there are various number types this could be useful for (int, long, BigDecimal), too, which would require additional implementations. That's why I think a library should contain these.

In any case, even with the code in comment #19, I don't consider my original request fixed.

However, regarding the issue, I can imagine the addition of an IntRange subclass which would provide such functionality. But that's just a quick thought.

gissuebot commented 10 years ago

Original comment posted by ted.zlatanov on 2011-05-09 at 02:20 PM


By far the most common Range->Set usage for me would be to generate an integer set. So I think it would be a nice bit of syntactic sugar to provide as asIntegerSet() method which simply uses asSet(DiscreteDomains.integers()) behind the scenes. Obviously the user can write the method themselves but this way it's explicitly provided in the direct API instead of specified by the user in an indirect way.

Also it's too bad that only continuous ranges were provided. R-trees or inversion lists would have been more generally useful.

gissuebot commented 10 years ago

Original comment posted by cgdecker on 2011-05-09 at 10:01 PM


Also, while asSet is returning an immutable set with a fixed iteration order, it is irritating as a reader would expect a range to return some kind of list or another iterable that is not looking as it wouldn't care about ordering.

It returns an ImmutableSortedSet, so the fact that it's ordered should be clear.

So I think it would be a nice bit of syntactic sugar to provide as asIntegerSet() method which simply uses asSet(DiscreteDomains.integers()) behind the scenes.

It doesn't make sense to have an asIntegerSet() method on a Range<Foo>, were it even possible.

gissuebot commented 10 years ago

Original comment posted by ted.zlatanov on 2011-05-10 at 01:26 AM


It doesn't make sense to have an asIntegerSet() method on a Range<Foo>, were it even possible.

I think it makes sense for any Range<Number> or at least any numbers that can be mapped on the number line (forgive my ignorance of all the Number variations in Java).

gissuebot commented 10 years ago

Original comment posted by cgdecker on 2011-05-10 at 02:19 AM


My point was that Range is a general-purpose class that can be used with many things that aren't numbers. In any case, I think using static imports might make this read a little more like you want: range.asSet(integers())

gissuebot commented 10 years ago

Original comment posted by gscerbak on 2011-05-10 at 12:08 PM


I think this issue was about creating a convenient method for iterating. The proposed Range is more about mathematical notion of intervals. I think there already is such a feature - it is the AbstractLinkedIterator. I created an Iterable around it, which takes a first value and a function for computing the next value. I added static factory method for this Iterable, which I call unfold.

Examples:

@Test
public final void numsFrom() {
    assertThat(limit(unfold(5, successor), 3), hasItems(5, 6, 7));
}

@Test
public final void squares() {
    final Iterable<Integer> squares = transform(unfold(0, successor),
            new Function<Integer, Integer>() {
                @Override
                public Integer apply(final Integer input) {
                    return input * input;
                }
            });
    assertThat(limit(squares, 6), hasItems(0, 1, 4, 9, 16, 25));
}

@Test
public final void primes() {
    final Iterable<Integer> primes = unfold(2, new Function<Integer, Integer>() {
        private Predicate<Integer> sieve = alwaysTrue();

        @Override
        public Integer apply(Integer input) {
            while (!sieve.apply(input)) {
                input++;
            }
            final Integer prime = input;
            sieve = and(sieve, new Predicate<Integer>() {
                @Override
                public boolean apply(final Integer number) {
                    return number % prime != 0;
                }
            });
            return prime;
        }
    });
    assertThat(get(primes, 300), is(1987));
}

I think this might be a better temporary solution until it is clear what sort of Ranges people really want.

gissuebot commented 10 years ago

Original comment posted by ted.zlatanov on 2011-05-12 at 02:37 PM


Should I make a request for a NumberRange class? The specific case where the range is numeric has specific uses and optimizations that can't apply to general Ranges.

gissuebot commented 10 years ago

Original comment posted by gscerbak on 2011-05-12 at 02:53 PM


I don't think NumberRange is a good idea. The thing that people are asking for is Range for DiscreteDomain, especially with convenient Iterables. It looks similar to IndexedIterable, which is in the idea graveyard, but I don't know for what reasons. As AbstractLinkedIterator got into the library, it could be a viable alternative to implement something similar to such Iterable with it, as I showed above.

gissuebot commented 10 years ago

Original comment posted by ted.zlatanov on 2011-05-12 at 03:05 PM


OK, I'll go with that since it's so easy to use Range as a NumberRange. Thanks.

gissuebot commented 10 years ago

Original comment posted by g.e.dejong on 2011-06-06 at 12:50 PM


I think the ranges is an excellent addition to the library. All new language or library additions will be hard to grok at first. Anyways, I had a small question regarding ranges:

I previously implemented some utilities to handle Ranges (intervals in our system). For simplicity sake, we forced intervals to have a closed left and open right. We stumbled a bit when we wanted to decide whether an open interval contains another open interval when the bounds are equal (eg. [0, 1) and [0, 1)). As far as I know (I'm not a mathematician), this is only true when the measures of both intervals are equal, which cannot be guaranteed. Therefor, I opted to have it return false, but I was wondering why Guava decided to return true instead.

With kind regards,