Подсчет и печать уникальных элементов списка в одной цепочке с использованием потоков Java - PullRequest
6 голосов
/ 06 июня 2019

Я пытаюсь добиться этого, используя только функциональные программные конструкции (потоки, коллекторы, лямбда-выражения).

Скажем, list - это String[]:

{"Apple", "Samsung", "LG", "Oppo", "Apple", "Huawei", "Oppo"}

Я хочу распечатать отдельный список торговых марок из этого массива и пронумеровать их, например:

1. Apple
2. Huawei
3. LG
4. Oppo
5. Samsung

Я могу распечатать уникальные элементы (отсортированные):

Stream.of(list)
    .distinct()
    .sorted()
    .forEach(System.out::println);

Но это не показывает предыдущий счетчик.Я пытался Collectors.counting(), но это, конечно, не сработало.

Любая помощь экспертов ФП?


Редактировать : Я понимаю некоторые другие вопросыспросил об итерации по потоку с индексами.Однако я не могу просто сделать:

IntStream.range(0, list.length)
        .mapToObj(a -> (a+1) + ". " + list[a])
        .collect(Collectors.toList())
        .forEach(System.out::println);

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

Ответы [ 4 ]

3 голосов
/ 06 июня 2019

Вы можете сделать это в функциональном стиле, без побочных эффектов, настроив один поток, который сортирует элементы, и второй (бесконечный) поток для номеров строк, а затем объедините потоки с zip.Zip - очень распространенная утилита для функционального программирования.

В этом примере используется функция почтового индекса guava.Код ленив (единственная терминальная операция - forEach, примененный к потоку, возвращенному из zip-вызова), и никаких побочных эффектов нет.

import java.util.stream.Stream;
import com.google.common.collect.Streams;

public class ZipExample {
    public static void main(String[] args) {
        String[] a = {"Apple", "Samsung", "LG", "Oppo", "Apple", "Huawei", "Oppo"};
        Stream<String> items = Stream.of(a).sorted().distinct();
        Stream<Integer> indices = Stream.iterate(1, i -> i + 1);
        Streams.zip(items, indices, 
            (item, index) -> index + ". " + item)
            .forEach(System.out::println);
    }
}

Распечатывается

1. Apple
2. Huawei
3. LG
4. Oppo
5. Samsung
3 голосов
/ 06 июня 2019

Более простым решением может быть использование Set в качестве коллекции для обеспечения доступа к уникальным строкам и увеличения индекса вместе со всеми такими элементами:

String[] list = {"Apple", "Samsung", "LG", "Oppo", "Apple", "Huawei", "Oppo"};
Set<String> setOfStrings = new HashSet<>(); // final strings
AtomicInteger index = new AtomicInteger(1); // index
Arrays.stream(list)
        .filter(setOfStrings::add) // use the return type of Set.add
        .forEach(str -> System.out.println(index.getAndIncrement() + ". " + str));

Редактировать : (благодаря Хольгеру) И вместо индекса, сохраняемого как отдельная переменная, можно использовать размер коллекции как:

Arrays.stream(list)
        .filter(setOfStrings::add) // use the return type of Set.add
        .forEach(str -> System.out.println(setOfStrings.size() + ". " + str)); // use size for index
3 голосов
/ 06 июня 2019

Редактировать после комментария Хольгера: Если вы просто хотите, чтобы отдельные бренды были в порядке, встречающемся в массиве, все, что вам нужно сделать, это пропустить операцию сортировки.

    String[] list = {"Apple", "Samsung", "LG", "Oppo", "Apple", "Huawei", "Oppo"};
    Stream.of(list)
            .distinct()
            .forEach(System.out::println);

Вывод:

Apple
Samsung
LG
Oppo
Huawei

Как сказал Хольгер, distinct поддерживает порядок встречи , если поток имел порядок встречи перед операцией distinct (которую ваш потокhas).

Мне действительно нужен предыдущий счетчик (1. 2. 3. 4. 5.).

Мой предпочтительный способ сделать это - использовать расширенныйfor петля и без лямбды:

    int counter = 0;
    for (String brand : new LinkedHashSet<>(Arrays.asList(list))) {
        counter++;
        System.out.println("" + counter + ". " + brand);
    }

Вывод:

1. Apple
2. Samsung
3. LG
4. Oppo
5. Huawei

Дальнейшие комментарии от вас:

IМне интересно, что такое «идеальное решение FP».

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

    List<String> distinctBrands
            = new ArrayList<>(new LinkedHashSet<>(Arrays.asList(list)));
    IntStream.range(0, distinctBrands.size())
            .mapToObj(index -> "" + (index + 1) + ". " + distinctBrands.get(index))
            .forEach(System.out::println);

A LinkedHashSet поддерживает порядок вставки и удаляет дубликаты.Вывод такой же, как и раньше.

2 голосов
/ 07 июня 2019

с гуавой:

Streams.mapWithIndex(Stream.of(list).distinct(), (s, i) -> (i + 1) + ". " + s)
       .forEach(System.out::println);

Выход:

1. Apple
2. Samsung
3. LG
4. Oppo
5. Huawei
...