Подтвердить что ты не робот

Когда следует использовать IntStream.range в Java?

Я хотел бы знать, когда я могу эффективно использовать IntStream.range. У меня есть три причины, почему я не уверен, насколько полезен IntStream.range.

(Пожалуйста, подумайте о начале и конце как целые числа.)

  • Если мне нужен массив, [start, start+1, ..., end-2, end-1], код ниже намного быстрее.

    int[] arr = new int[end - start];
    int index = 0;
    for(int i = start; i < end; i++)
        arr[index++] = i;
    

    Возможно, это связано с тем, что toArray() в IntStream.range(start, end).toArray() работает очень медленно.

  • Я использую MersenneTwister для перетасовки массивов. (Я загрузил класс MersenneTwister в Интернете.) Я не думаю, что есть способ перетасовать IntStream с помощью MersenneTwister.

  • Я не думаю, что полезно получить только цифры int от start до end-1. Я могу использовать for(int i = start; i < end; i++), что кажется проще и не медленнее.

Не могли бы вы рассказать мне, когда я должен выбрать IntStream.range?

4b9b3361

Ответ 1

Существует несколько вариантов использования IntStream.range.

Один из них заключается в том, чтобы сами использовать значения int:

IntStream.range(start, end).filter(i -> isPrime(i))....

Другой способ сделать что-то N раз:

IntStream.range(0, N).forEach(this::doSomething);

Ваш случай (1) должен создать массив, заполненный диапазоном:

int[] arr = IntStream.range(start, end).toArray();

Вы говорите, что это "очень медленно", но, как и другие респонденты, я подозреваю вашу контрольную методологию. Для небольших массивов на потоковой установке действительно больше накладных расходов, но это должно быть настолько мало, чтобы быть незаметным. Для больших массивов накладные расходы должны быть незначительными, так как заполнение большого массива преобладает в полосе пропускания памяти.

Иногда вам нужно заполнить существующий массив. Вы можете сделать это следующим образом:

int[] arr = new int[end - start];
IntStream.range(0, end - start).forEach(i -> arr[i] = i + start);

Есть способ утилиты Arrays.setAll, который может сделать это еще более кратко:

int[] arr = new int[end - start];
Arrays.setAll(i -> i + start);

Существует также Arrays.parallelSetAll, который может заполнять существующий массив параллельно. Внутри он просто использует IntStream и вызывает на нем parallel(). Это должно обеспечить ускорение для большого массива в многоядерной системе.

Я обнаружил, что большое количество моих ответов на Qaru включает использование IntStream.range. Вы можете искать их, используя эти критерии поиска в окне поиска:

user:1441122 IntStream.range

Одно приложение IntStream.range, которое я нахожу особенно полезным, - это работать с элементами массива, где в вычислении участвуют индексы массива, а также значения массива. Там целый класс таких проблем.

Например, предположим, что вы хотите найти места для увеличения числа чисел в массиве. Результатом является массив индексов в первый массив, где каждый индекс указывает на начало прогона.

Чтобы вычислить это, обратите внимание, что запуск начинается с того места, где значение меньше предыдущего. (Запуск также начинается с местоположения 0). Таким образом:

    int[] arr = { 1, 3, 5, 7, 9, 2, 4, 6, 3, 5, 0 };
    int[] runs = IntStream.range(0, arr.length)
                          .filter(i -> i == 0 || arr[i-1] > arr[i])
                          .toArray();
    System.out.println(Arrays.toString(runs));

    [0, 5, 8, 10]

Конечно, вы можете сделать это с помощью цикла for, но я считаю, что использование IntStream предпочтительнее во многих случаях. Например, легко хранить неизвестное количество результатов в массиве с помощью toArray(), тогда как с циклом for вы должны обрабатывать копирование и изменение размера, что отвлекает от основной логики цикла.

Наконец, гораздо проще выполнять параллельные вычисления IntStream.range.

Ответ 2

Вот пример:

public class Test {

    public static void main(String[] args) {
        System.out.println(sum(LongStream.of(40,2))); // call A
        System.out.println(sum(LongStream.range(1,100_000_000))); //call B
    }

    public static long sum(LongStream in) {
        return in.sum();
    }

}

Итак, посмотрим, что делает sum(): он подсчитывает сумму произвольного потока чисел. Мы называем это двумя разными способами: один раз с явным списком чисел и один раз с диапазоном.

Если у вас только call A, у вас может возникнуть соблазн поместить два числа в массив и передать его в sum(), но это явно не вариант с call B (у вас закончится нехватка памяти). Аналогично, вы можете просто передать начало и конец для call B, но тогда вы не смогли бы поддержать случай call A.

Итак, чтобы подвести итог, диапазоны полезны здесь, потому что:

  • Нам нужно передать их между методами
  • Целевой метод работает не только на диапазонах, но и на любом потоке чисел
  • Но он работает только с отдельными номерами потока, читая их последовательно. (Вот почему перетасовка с потоками - ужасная идея в целом.)

Существует также аргумент читаемости: код с использованием потоков может быть намного более кратким, чем циклы, и, следовательно, более читаемым, но я хотел показать пример, где решение, основанное на IntStrean, также функционально превосходит.

Я использовал LongStream, чтобы подчеркнуть точку, но то же самое для IntStream

И да, для простого суммирования это может показаться немного излишним, но рассмотрим, например, пробку коллектора

Ответ 3

IntStream.range возвращает диапазон целых чисел в виде потока, поэтому вы можете обрабатывать поток через него.

как квадрат каждого элемента

IntStream.range(1, 10).map(i -> i * i);  

Ответ 4

Это полностью зависит от варианта использования. Тем не менее, синтаксис и поток API добавляет много простых лайнеров, которые могут определенно заменить обычные циклы.

IntStream действительно полезен и синтаксический сахар в некоторых случаях,

IntStream.range(1, 101).sum();
IntStream.range(1, 101).average();
IntStream.range(1, 101).filter(i -> i % 2 == 0).count();
//... and so on

Что бы вы ни делали с IntStream, вы можете делать с обычными циклами. Поскольку один лайнер является более точным, чтобы понимать и поддерживать.

Для отрицательных циклов мы не можем использовать IntStream#range, он работает только с положительным шагом. Итак, следующее невозможно,

for(int i = 100; i > 1; i--) {
    // Negative loop
}
  • Случай 1: Да, обычный цикл в этом случае намного быстрее, поскольку toArray имеет немного служебных данных.

  • Случай 2: Я ничего не знаю об этом, мои извинения.

  • Случай 3: IntStream совсем не медленный, IntStream.range и обычный цикл почти одинаковы с точки зрения производительности.

Смотрите:

Ответ 5

В принципе, если вы хотите выполнять операции Stream, вы можете использовать метод range(). Например, использовать concurrency или использовать map() или reduce(). Тогда вам лучше с IntStream.

Например:

IntStream.range(1, 5).parallel().forEach(i -> heavyOperation());

Или:

IntStream.range(1, 5).reduce(1, (x, y) -> x * y)  
// > 24

Вы можете получить второй пример также с помощью цикла for, но вам нужны промежуточные переменные и т.д.

Кроме того, если вы хотите, чтобы первое совпадение, например, вы могли использовать findFirst() и кузены, чтобы остановить потребление остальной части Stream

Ответ 6

Вот несколько отличий, которые приходят мне в голову между IntStream.range и традиционными для циклов:

  • IntStream оцениваются лениво, конвейер пересекается при вызове операции терминала. Для циклов оценивать на каждой итерации.
  • IntStream предоставит вам некоторые функции, которые обычно применяются к диапазону int, например sum и avg.
  • IntStream позволит вам кодировать несколько операций над диапазоном int функциональным способом, который читается более свободно - особенно, если у вас много операций.

В основном используйте IntStream, когда вам будет полезно одно или несколько из этих различий.

Но помните, что перетасовка звука Stream довольно странная, поскольку Stream не является структурой данных, и поэтому нет смысла перетасовывать ее (в случае, если вы планировали создать специальный IntSupplier). Вместо этого перетасовывайте результат.

Что касается производительности, в то время как может быть несколько накладных расходов, вы все равно повторите N раз в обоих случаях и не должны больше заботиться о них.

Ответ 7

Вы можете реализовать свой Mersenne Twister как поток Iterator и из этого.