Как выполнить вложенные операторы if с помощью Java 8/лямбда?

У меня есть следующий код, и я хотел бы реализовать его с помощью лямбда-функций просто для удовольствия. Можно ли это сделать с помощью основных агрегатных операций?

List<Integer> result = new ArrayList<>();

for (int i = 1; i <= 10; i++) {
    if (10 % i == 0) {
        result.add(i);
        if (i != 5) {
            result.add(10 / i);
        }
    }
}

Использование лямбды:

List<Integer> result = IntStream.rangeClosed(1, 10)
                                .boxed()
                                .filter(i -> 10 % i == 0)
                                // a map or forEach function here?
                                // .map(return 10 / i -> if i != 5)
                                .collect(Collectors.toList());

person moon    schedule 25.07.2015    source источник


Ответы (5)


Важным наблюдением здесь является то, что ваша проблема связана с неизоморфным преобразованием: один входной элемент может отображаться в ноль, один или два выходных элемента. Всякий раз, когда вы заметите это, вы должны немедленно начать искать решение, которое включает flatMap вместо map, потому что это единственный способ добиться такого общего преобразования. В вашем конкретном случае вы можете сначала применить filter для сопоставления элементов один к нулю, затем flatMap для сопоставления один к двум:

List<Integer> result =
    IntStream.rangeClosed(1, 10)
             .filter(i -> 10 % i == 0)
             .flatMap(i -> i == 5 ? IntStream.of(i) : IntStream.of(i, 10 / i))
             .boxed()
             .collect(toList());

(при условии import static java.util.stream.Collectors.toList)

person Marko Topolnik    schedule 25.07.2015
comment
Мне нравится, как вы не только отвечаете на вопрос, но и учите, как думать о проблеме, чтобы найти решение. - person marcus; 19.11.2016

Вы можете объявить тело для лямбды. Например:

Runnable run = () -> System.out.println("Hey");

Может быть

Runnable run = () -> {
    System.out.println("Hey");
};

Внутри этого тела вы можете создавать вложенные операторы:

Runnable run = () -> {
    int num = 5;

    if(num == 5) {
        System.out.println("Hey");
    }
};
person Dioxin    schedule 25.07.2015
comment
но можно ли это сделать с map, filter и т. д.? Я пытаюсь изучить основы лямбда-функций. Спасибо. - person moon; 25.07.2015
comment
@ Z-1 Я не уверен, что фильтры могут это сделать, но синтаксис будет .filter( i -> { return yourLogic; } ) - person RAnders00; 25.07.2015
comment
@Z-1 Лямбда-выражение возникает из-за использования функционального интерфейса (интерфейс только с 1 методом abstract, например Runnable; может иметь несколько методов default). Например, Thread принимает Runnable в своем конструкторе. Мы могли бы написать new Thread(() -> { });. Вы даже можете создавать свои собственные функциональные интерфейсы. Итак, чтобы ответить можно сделать это с помощью map и filter: да. Он работает для всех лямбд. - person Dioxin; 25.07.2015

Используйте flatMap, когда вы пытаетесь добавить элементы в конвейер или сопоставление 1-ко-многим. Карта представляет собой сопоставление один к одному.

ArrayList<Integer> result = (ArrayList<Integer>) IntStream.rangeClosed(1, 10)
                .boxed()
                .filter(i -> 10 % i == 0)
                .flatMap((Integer i) -> {return i!=5 ? Stream.of(i, (10/i)):Stream.of(i);})
                .collect(Collectors.toList());

Это приводит к тому же списку, что и

ArrayList<Integer> result2 = new ArrayList<Integer>();

        for (int i = 1; i <= 10; i++) {
            if (10 % i == 0) {
                result2.add(i);
                if (i != 5) {
                    result2.add(10 / i);
                }
            }
        }

Если вам интересно, какой способ быстрее, метод цикла примерно в 3 раза быстрее, чем использование потоков.

Benchmark                     Mode  Cnt      Score     Error  Units
testStreams.Bench.loops       avgt    5     75.221 ±   0.576  ns/op
testStreams.Bench.streams     avgt    5    257.713 ±  13.125  ns/op
person Mantis    schedule 25.07.2015
comment
Это интересно. Спасибо за эталон. Я также заметил, что Streams намного медленнее, чем традиционный цикл for. - person moon; 25.07.2015
comment
На самом деле зависит от приложения, операции с целыми числами являются одними из самых простых операций, которые вы можете выполнить, для того, что вы делали здесь, накладные расходы при настройке потока слишком высоки. Я нашел второй ответ в этом сообщении полезно. - person Mantis; 25.07.2015
comment
накладные расходы потока действительно уменьшаются по мере увеличения диапазона цикла. хотя в этом случае не совсем исчезает. - person the8472; 26.07.2015

Ты можешь сделать это:

List<Integer> result1 = IntStream
    .rangeClosed(1, 10)
    .boxed()
    .filter(i -> 10 % i == 0)
    .map(i -> (i != 5 ? Stream.of(i, 10 / i) : Stream.of(i)))
    .flatMap(Function.identity())
    .collect(Collectors.toList());
person Mrinal    schedule 25.07.2015

Попробуйте использовать flatMap:

List<Integer> result = IntStream.rangeClosed(1, 10)
        .boxed()
        .flatMap((i) -> {
            List<Integer> results = new ArrayList<>();
            if (10 % i == 0) {
                results.add(i);
                if (i != 5) {
                    results.add(10 / i);
                }
            }
            return results.stream();
        })
        .collect(Collectors.toList());

См. http://ideone.com/EOBiEP.

person Danil Gaponov    schedule 25.07.2015