简体   繁体   English

使用 Java Streams 计算并打印一个链中的唯一列表项

[英]Count and print unique list items in one chain using Java Streams

I'm trying to achieve this using only functional programming constructs (Streams, Collectors, lambda expressions).我试图仅使用函数式编程结构(流、收集器、lambda 表达式)来实现这一点。

Let's say list is a String[] :假设list是一个String[]

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

I want to print out a distinct list of brand names from this array, and number them, ie:我想从这个数组中打印出一个不同的品牌名称列表,并对它们进行编号,即:

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

I can print out the unique elements (sorted):我可以打印出独特的元素(排序):

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

But this does not show the preceding counter.但这并没有显示前面的计数器。 I tried Collectors.counting() but that, of course, didn't work.我试过Collectors.counting()但那当然没有用。

Any help from FP experts? FP专家有什么帮助吗?


Edit : I understand some other questions have asked about iterating over a stream with indices.编辑:我了解其他一些关于使用索引迭代流的问题。 However, I can't simply do:但是,我不能简单地这样做:

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

because the original array contains duplicate elements.因为原始数组包含重复元素。 There might also be other map and filter operations I may need to perform on the stream before I print the result.在打印结果之前,我可能还需要对流执行其他mapfilter操作。

Edit after Holger's comment: If you just want the distinct brands in the order encountered in the array, all you need to do is to omit the sort operation.在 Holger 的评论后编辑:如果您只想在数组中遇到的顺序中的不同品牌,您需要做的就是省略排序操作。

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

Output is:输出是:

 Apple Samsung LG Oppo Huawei

As Holger said, distinct maintains encounter order if the stream had encounter order before the distinct operation (which your stream has).正如 Holger 所说,如果流在distinct操作(您的流具有)之前遇到顺序,则distinct维护遇到顺序。

I actually need the preceding counter (1. 2. 3. 4. 5.).我实际上需要前面的计数器(1. 2. 3. 4. 5.)。

My preferred way of doing it is using an enhanced for loop and no lambdas:我首选的方法是使用增强for循环并且不使用 lambda:

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

Output is:输出是:

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

Further comment from you:您的进一步评论:

I'm wondering what's the "ideal FP" solution.我想知道什么是“理想的 FP”解决方案。

I'm not necessarily convinced that any good purely functional programming solution exists.我不一定相信存在任何好的纯函数式编程解决方案。 My attempt would be, inspired from the linked original question and its answers:我的尝试将受到链接的原始问题及其答案的启发:

    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 maintains insertion order and drops duplicates. LinkedHashSet维护插入顺序并删除重复项。 Output is the same as before.输出和以前一样。

You can do this in a functional style, without side-effects, by setting up one stream that sorts the items and a second stream for the line numbers, then combining the streams with zip.您可以通过设置一个对项目进行排序的流和第二个用于行号的流,然后将流与 zip 结合起来,以一种没有副作用的功能样式来执行此操作。

This example uses guava's zip function.这个例子使用了 guava 的 zip 函数。 Zip is a very common utility for functional programming. 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);
    }
}

It prints out它打印出来

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

Rewritten as one expression it would look like:重写为一个表达式,它看起来像:

Streams.zip(
    Stream.of(a).sorted().distinct(), 
    Stream.iterate(1, i -> i + 1), 
    (item, index) -> index + ". " + item)
    .forEach(System.out::println);

A simpler solution could be to use a Set as a collection to ensure unique strings are accessed and incrementing the index along with for all such elements:一个更简单的解决方案可能是使用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));

Edit : (Thanks to Holger) And instead of the index maintained as a separate variable, one can use the size of the collection as:编辑:(感谢 Holger)而不是将索引维护为单独的变量,可以将集合的大小用作:

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

With Guava:与番石榴:

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

Output:输出:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM