Как лучше всего узнать количество / длину / размер итератора?

96

Есть ли "вычислительно" быстрый способ получить счетчик итератора?

int i = 0;
for ( ; some_iterator.hasNext() ; ++i ) some_iterator.next();

... кажется пустой тратой циклов процессора.

Зак
источник
2
Итератор не обязательно соответствует чему-то с «счетчиком» ...
Оливер Чарльзуорт
Итераторы такие, какие они есть; для перехода к следующему объекту коллекции (это может быть что-нибудь вроде набора, массива и т. д.). Почему им нужно сообщать размер, когда им все равно, для чего они пытаются выполнить итерацию? to provide an implementation-independent method for access, in which the user does not need to know whether the underlying implementation is some form of array or of linked list, and allows the user go through the collection without explicit indexing. penguin.ewu.edu/~trolfe/LinkedSort/Iterator.html
ecle

Ответы:

67

Если у вас только что есть итератор, то это то, что вам нужно сделать - он не знает, сколько элементов осталось для итерации, поэтому вы не можете запросить его для этого результата. Есть служебные методы, которые, похоже, делают это (например, Iterators.size()в Guava), но ниже они просто выполняют примерно ту же операцию.

Однако многие итераторы поступают из коллекций, размер которых часто можно запросить. И если это пользовательский класс, для которого вы получаете итератор, вы можете подумать о предоставлении метода size () для этого класса.

Короче говоря, в ситуации, когда у вас есть только итератор, лучшего способа нет, но гораздо чаще у вас есть доступ к базовой коллекции или объекту, из которого вы можете напрямую получить размер.

Майкл Берри
источник
Остерегайтесь побочного эффекта Iterators.size(...)(упомянутого в других комментариях ниже и в java-doc): «Возвращает количество элементов, оставшихся в итераторе. Итератор останется исчерпанным: его метод hasNext () вернет false». Это означает, что вы больше не можете использовать итератор после этого. Lists.newArrayList(some_iterator);может помочь.
MichaelCkr
91

Использование библиотеки Guava :

int size = Iterators.size(iterator);

Внутри он просто перебирает все элементы, так что это просто для удобства.

Андрейс
источник
8
Это очень элегантно. Просто помните, что вы потребляете свой итератор (т.е. итератор после этого будет пустым)
lolski
1
Это не «вычислительно быстрый» метод, это удобный метод, который имеет нежелательный побочный эффект использования итератора.
Зак
Не могли бы вы объяснить, как это работает? @Andrejs List <Tuple2 <String, Integer >> wordCountsWithGroupByKey = wordsPairRdd.groupByKey () .mapValues ​​(intIterable -> Iterables.size (intIterable)). Collect (); System.out.println ("wordCountsWithGroupByKey:" + wordCountsWithGroupByKey); «Iterables.size (intIterable)?
Адитья Верма,
15

Ваш код выдаст вам исключение, когда вы дойдете до конца итератора. Вы могли сделать:

int i = 0;
while(iterator.hasNext()) {
    i++;
    iterator.next();
}

Если бы у вас был доступ к базовой коллекции, вы могли бы вызвать coll.size()...

ИЗМЕНИТЬ ОК, вы изменили ...

ассилий
источник
насколько это эффективно? что, если итератор похож на миллион значений?
Micro
4
@Micro технически итератор может быть бесконечным - в этом случае цикл будет продолжаться вечно.
assylias
12

Вам всегда придется повторяться. Тем не менее, вы можете использовать Java 8, 9 для подсчета без явного цикла:

Iterable<Integer> newIterable = () -> iter;
long count = StreamSupport.stream(newIterable.spliterator(), false).count();

Вот тест:

public static void main(String[] args) throws IOException {
    Iterator<Integer> iter = Arrays.asList(1, 2, 3, 4, 5).iterator();
    Iterable<Integer> newIterable = () -> iter;
    long count = StreamSupport.stream(newIterable.spliterator(), false).count();
    System.out.println(count);
}

Это печатает:

5

Интересно, что вы можете распараллелить операцию подсчета здесь, изменив parallelфлаг этого вызова:

long count = StreamSupport.stream(newIterable.spliterator(), *true*).count();
gil.fernandes
источник
8

Использование библиотеки Guava , другой вариант заключается в преобразовании Iterableк List.

List list = Lists.newArrayList(some_iterator);
int count = list.size();

Используйте это, если вам также нужно получить доступ к элементам итератора после получения его размера. Используя, Iterators.size()вы больше не можете получить доступ к повторяющимся элементам.

ташуха
источник
2
@LoveToCode Менее эффективен, чем пример по исходному вопросу
Winter
2
Конечно, создание нового объекта со всеми элементами происходит медленнее, чем простая итерация и удаление. IMHO, это решение однострочное, улучшающее читаемость кода. Я часто использую его для коллекций с небольшим количеством элементов (до 1000) или когда скорость не является проблемой.
tashuhka
7

Если все, что у вас есть, - это итератор, то нет, "лучшего" пути нет. Если итератор поступает из коллекции, вы можете использовать размер.

Имейте в виду, что Iterator - это просто интерфейс для просмотра различных значений, у вас очень хорошо будет такой код, как этот

    new Iterator<Long>() {
        final Random r = new Random();
        @Override
        public boolean hasNext() {
            return true;
        }

        @Override
        public Long next() {
            return r.nextLong();
        }

        @Override
        public void remove() {
            throw new IllegalArgumentException("Not implemented");
        }
    };

или

    new Iterator<BigInteger>() {
        BigInteger next = BigInteger.ZERO;

        @Override
        public boolean hasNext() {
            return true;
        }

        @Override
        public BigInteger next() {
            BigInteger current = next;
            next = next.add(BigInteger.ONE);
            return current;
        }

        @Override
        public void remove() {
            throw new IllegalArgumentException("Not implemented");
        }
    }; 
Роджер Линдсьо
источник
4

Нет более эффективного способа, если все, что у вас есть, - это итератор. И если итератор можно использовать только один раз, то получить счетчик до того, как вы получите содержимое итератора ... проблематично.

Решение состоит в том, чтобы либо изменить ваше приложение, чтобы оно не нуждалось в подсчете, либо получить счет каким-либо другим способом. (Например, передатьCollection а не Iterator...)

Стивен С
источник
0

для Java 8 вы можете использовать,

public static int getIteratorSize(Iterator iterator){
        AtomicInteger count = new AtomicInteger(0);
        iterator.forEachRemaining(element -> {
            count.incrementAndGet();
        });
        return count.get();
    }
Робби70
источник
-5

Объект итератора содержит то же количество элементов, что и ваша коллекция.

List<E> a =...;
Iterator<E> i = a.iterator();
int size = a.size();//Because iterators size is equal to list a's size.

Но вместо того, чтобы получать размер итератора и перебирать индекс 0 до этого размера, лучше перебирать метод next () итератора.

Чандра Сехар
источник
Что, если у нас нет a, а только i?
Tvde1