Какой самый короткий способ получить итератор для диапазона целых чисел в Java? Другими словами, реализуйте следующее:
/**
* Returns an Iterator over the integers from first to first+count.
*/
Iterator<Integer> iterator(Integer first, Integer count);
Что-то вроде
(first..first+count).iterator()
Это фреймворк, который требует, чтобы я написал итератор.
Ясность всегда превосходит краткость, и существует множество доступных классов Range, которые включают итерацию.




Прямое выполнение домашнего задания:
List<Integer> ints = new ArrayList<Integer>();
for (int i = 0; i < count; i++) {
ints.add(first + i);
}
Это правильный вариант, если вы серьезно относитесь к «кратчайшему», за исключением того, что вам не хватает «return ints.iterator ();» ;-)
Кроме того, это нехватка памяти.
Я думаю, это немного снисходительно называть это домашним заданием. Это очень частый вариант использования, и, как показано ниже, существуют лучшие реализации, чем это (хотя не все из них были возможны на момент написания).
Не проверено. Сопоставление этого с «min, count» оставлено в качестве упражнения для читателя.
public class IntRangeIterator implements Iterator<Integer> {
private int nextValue;
private final int max;
public IntRangeIterator(int min, int max) {
if (min > max) {
throw new IllegalArgumentException("min must be <= max");
}
this.nextValue = min;
this.max = max;
}
public boolean hasNext() {
return nextValue <= max;
}
public Integer next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return Integer.valueOf(nextValue++);
}
public void remove() {
throw new UnsupportedOperationException();
}
}
это выглядит почти идентично тому, что я только что написал для себя :)
Если вам действительно нужен кратчайший объем кода, то ответ Бомба подойдет. Однако без уважительной причины засасывает память. Если вы хотите реализовать это самостоятельно, это будет примерно так:
import java.util.*;
public class IntegerRange implements Iterator<Integer>
{
private final int start;
private final int count;
private int position = -1;
public IntegerRange(int start, int count)
{
this.start = start;
this.count = count;
}
public boolean hasNext()
{
return position+1 < count;
}
public Integer next()
{
if (position+1 >= count)
{
throw new NoSuchElementException();
}
position++;
return start + position;
}
public void remove()
{
throw new UnsupportedOperationException();
}
}
Обычно считается хорошим стилем передавать Collection и друзьям вместо Iterator (см. эта запись в FAQ), поэтому я бы порекомендовал что-то вроде
public final class IntegerRange implements Set<Integer> {
final LinkedHashSet<Integer> backingList;
public IntegerRange(final int start, final int count) {
backingList = new LinkedHashSet(count, 1.0f);
for (int i=0; i < count; i++) {
backingList.set(i, start + i);
}
}
/** Insert a bunch of delegation methods here */
}
а затем просто используйте .iterator(), когда вам нужно передать Iterator в любую используемую вами структуру.
ОБНОВЛЕНИЕ: Очевидно, этот код не ленивый. Если вы не можете позволить себе дополнительные накладные расходы на память для хранения (потенциально) 2 ^ 32-1 Integer, вам следует использовать другое решение. Кроме того, ничто в отношении типа не гарантирует, что диапазон будет отсортирован (даже если это так, в зависимости от реализации). Если вам нужно гарантировать сортировку, вы можете рассмотреть возможность реализации SortedSet и поддержки его с помощью TreeSet, но для построения диапазона потребуется больше времени. Честно говоря, если вы так озабочены тем, чтобы детали были верны, возможно, стоит поискать библиотеку. Например, у гобелена есть внутренняя версия.
Зачем реализовывать это в наборе подложек? Это довольно легко реализовать, не сохраняя все целые числа (что может быть довольно интенсивным для памяти), если вам не нужно поддерживать add () и remove (). Я бы также добавил, что список был бы более подходящим (поскольку определенно есть некоторый порядок)
RE: List -> cretzel не указал ListIterator, а Iterator не дает никаких гарантий упорядочивания, поэтому я выбрал ближайшее сопоставление (Collection), хотя, оглядываясь назад, я бы использовал Set. RE: lazy -> казалось, что это намного больше работы, плюс Кретцель не упомянул о том, что не нуждается в поддержке remove ().
Запись в FAQ, на которую вы ссылаетесь, говорит нам, что мы не должны передавать итераторы вокруг коллекции объектов. Но если вас интересует не коллекция, а процесс итерации, передача Iterator вполне нормальна.
Я думаю, что Collection vs. Iterator действительно зависит. Ребята из Google, кажется, предпочитают Iterator для своих приложений: см. «Почему так много внимания уделяется итераторам и Iterables?» здесь: code.google.com/p/google-collections/wiki/Faq
Эта реализация не требует использования памяти.
/**
* @param begin inclusive
* @param end exclusive
* @return list of integers from begin to end
*/
public static List<Integer> range(final int begin, final int end) {
return new AbstractList<Integer>() {
@Override
public Integer get(int index) {
return begin + index;
}
@Override
public int size() {
return end - begin;
}
};
}
В Java 8 вы можете просто сказать:
IntStream.range(begin, end).iterator() // returns PrimitiveIterator.OfInt
или если вам нужна коробочная версия:
IntStream.range(begin, end).boxed().iterator() // returns Iterator<Integer>
Хотя сам список довольно короткий, он возвращает List вместо Iterable / Iterator, который предлагает гораздо больше методов интерфейса. Они здесь не реализованы, и это, в свою очередь, может привести к неожиданным результатам, если экземпляр списка используется в «непредвиденном» контексте. Таким образом, можно сказать, что эта реализация нарушает контракт интерфейса List. Кроме того, реализация итератора в AbstractList не так проста, как могла бы быть, и использует больше переменных экземпляра, чем необходимо, поэтому требуется небольшой объем памяти. Поэтому я бы предпочел прямую реализацию Iterable / Iterator.
@DanielBimschas Я думаю, это неправда. Этот класс действительно придерживается контракта списка неизменяемый, как определено в JavaDoc для java.util.Collection. В частности, он выбрасывает UnsupportedOperationException, если кто-то называет один из его «деструктивных» методов, например add() или remove(). Для получения дополнительных сведений см. Также JavaDoc java.util.AbstractList.
@Saintali fair point w.r.t. мой комментарий "нарушает контракт интерфейса списка". Пожалуйста, не обращайте внимания на эту часть. ИМХО мои другие комментарии остаются в силе.
Метод get() должен проверить, находится ли его параметр в допустимом диапазоне, а если нет (например, отрицательное значение или больше размера), бросить IndexOutofBoundsException.
Придираться. Список делает имеет след памяти (begin и end), но он постоянный.
Пример использования фреймворка guava. Обратите внимание, что это не материализует набор (хотя вы должны прочитать реализацию ContiguousSet, чтобы убедиться в этом).
import com.google.common.collect.ContiguousSet;
import com.google.common.collect.DiscreteDomain;
import com.google.common.collect.DiscreteDomains;
class RangeIterator {
public Iterator<Integer> range(int start, int length) {
assert length > 0;
Range<Integer> dim_range = Ranges.closedOpen(start, start + length);
DiscreteDomain<Integer> ints = DiscreteDomains.integers();
ContiguousSet<Integer> dim = dim_range.asSet(ints);
return dim.iterator();
}
}
самая короткая версия этого: Iterator<Integer> range = Ranges.closedOpen(start, end).asSet(DiscreteDomains.integers()).iterator();
@Miguel Метод Range#asSet(DiscreteDomain)устарела и была удалена в Guava 16.
@Synoli Iterator<Integer> range = ContiguousSet.create(Ranges.closedOpen(start, end), DiscreteDomains.integers()).iterator();
Пример использования потокового API в java 8:
int first = 0;
int count = 10;
Iterator<Integer> it = IntStream.range(first, first + count).iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
Без итератора это могло быть:
int first = 0;
int count = 10;
IntStream.range(first, first + count).forEach(i -> System.out.println(i));
Есть ли причина, по которой вы не можете просто «добавить 1» в классическом стиле? Просто интересно