【版本任你发】-7-任选俩人干活儿,多少种组合?

127 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第8天,点击查看活动详情

0.背景

  HR急匆匆跑到荡平宇宙软件开发部,大声喊:“发礼物了,挑两个人去下面搬礼物。”

  大家都很开心,现在坐在办公室里,有两个小组,A小组有小黄、小孙、老罗,B小组有小刘、小孟。

  老罗问小黄:“不是需要两个人吗?每个组挑一个人的话,有多少种情况呢?”

  小黄说:“那还不简单,我给你写一下。正好是六种情况。”

(小黄,小刘)(小黄,小孟)(小孙,小刘)(小孙,小孟)(老罗,小刘)(老罗,小孟)(小黄,小刘) \\ (小黄,小孟) \\ (小孙,小刘) \\ (小孙,小孟) \\ (老罗,小刘) \\ (老罗,小孟) \\

1.组合如何用代码表达呢?

  老罗接着问小黄:“你看啊,现在只有这么点儿人,如果人多了呢?你能写一个程序来表达这个逻辑吗?然后之后再加一个随机数,这样就不用每次还讨论谁去了。”

  小黄挠挠头,头发干枯分叉还多油。她写到:

public class PersonPairTest {

    public static class Person {

        public static final Person[] aGroupPersonList = {new Person("小黄"), new Person("小孙"), new Person("老罗")};

        public static final Person[] bGroupPersonList = {new Person("小刘"), new Person("小孟")};

        private String name;

        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    '}';
        }

        public Person(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }
    }

    public static void main(String[] args) {
        for (Person personInA : Person.aGroupPersonList) {
            for (Person personInB : Person.bGroupPersonList) {
                System.out.println("(" + personInA.getName() + "," + personInB.getName() + ")");
            }
        }
    }

}

2.换成函数式的思维呢?

  老罗微微一笑:“你看你这个双层for循环,如果变动了需求,是不是就要写注释、封装函数,一会儿里层加代码,一会儿外层加代码了?”

  小黄尴尬地说:“我只会这样,我听说过流,可以用流处理吗?但是一会儿是数组,一会儿是数组中的元素,一会儿又组合成数组了,我不会写这种的。”

  老罗不慌不忙地敲到:

public class PersonPairTest {

    public static class Person {

        public static final Person[] aGroupPersonList = {new Person("小黄"), new Person("小孙"), new Person("老罗")};

        public static final Person[] bGroupPersonList = {new Person("小刘"), new Person("小孟")};

        private String name;

        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    '}';
        }

        public Person(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }
    }

    public static void main(String[] args) {
        for (Person personInA : Person.aGroupPersonList) {
            for (Person personInB : Person.bGroupPersonList) {
                System.out.println("(" + personInA.getName() + "," + personInB.getName() + ")");
            }
        }

        System.out.println("-----------------------------------------");

        Stream<Person> aStream = Arrays.stream(Person.aGroupPersonList);

        List<String[]> collect = aStream.flatMap(
                        personInA -> Arrays.stream(Person.bGroupPersonList).map(
                                personInB -> new String[]{personInA.getName(), personInB.getName()}
                        )
                )

                .collect(Collectors.toList());


        for (String[] strings : collect) {
            StringJoiner stringJoiner = new StringJoiner(",", "(", ")");
            for (String string : strings) {
                stringJoiner.add(string);
            }
            System.out.println(stringJoiner.toString());
        }
    }

}

  小黄有一点疑惑:“为什么有个我不认识的 flatMap 它是做什么的呢?”

  老罗很欣慰地说:“你发现了重点,如果我们不用这个函数,我添加一些注释,我们看一下处理的过程”

        List<List<String[]>> pairListList = aStream.map(personInA -> {
            // 每个A组的人
            List<String[]> pairList = Arrays.stream(Person.bGroupPersonList).map(
                    // 都能和B组的人组成组合
                    personInB -> new String[]{personInA.getName(), personInB.getName()}
            ).collect(Collectors.toList());
            // 然后就返回了一个 List
            return pairList;
        }).collect(Collectors.toList());

  如此一来啊,我们其实获取到了结果,但是结果也是分组的结果,就像下面这样:

((小黄,小刘)(小黄,小孟)(小孙,小刘)(小孙,小孟)(老罗,小刘)(老罗,小孟))\begin{pmatrix} (小黄,小刘) & (小黄,小孟) \\ (小孙,小刘) & (小孙,小孟) \\ (老罗,小刘) & (老罗,小孟) \end{pmatrix}

  而我们,希望它是平的,就像下面这样:

((小黄,小刘)(小黄,小孟)(小孙,小刘)(小孙,小孟)(老罗,小刘)(老罗,小孟))\begin{pmatrix} (小黄,小刘) & (小黄,小孟) & (小孙,小刘) & (小孙,小孟) & (老罗,小刘) & (老罗,小孟) \end{pmatrix}

  咋办呢?我们就需要 flatMap ,看好了啊,我把 map 那一步的代码也附上,看看这一步做了什么。

        List<List<String[]>> pairListList = aStream.map(personInA -> {
            // 每个A组的人
            List<String[]> pairList = Arrays.stream(Person.bGroupPersonList).map(
                    // 都能和B组的人组成组合
                    personInB -> new String[]{personInA.getName(), personInB.getName()}
            ).collect(Collectors.toList());
            // 然后就返回了一个 List
            return pairList;
        }).collect(Collectors.toList());
        // 从这里开始,进行推平。
		List<String[]> pairList = pairListList.stream().flatMap(
                (List<String[]> complicatedList) -> {
                    // 把每个List的流 推平了,把List里的元素连接在一起。
            return complicatedList.stream();
        }).collect(Collectors.toList());

  推平这个流的句子,可以直接写成:

        // 把每个List的流 推平了 ,连接在一起。
        List<String[]> pairList = pairListList.stream()
        .flatMap(Collection::stream).collect(Collectors.toList());

3. flatMap 需要一个生成 StreamFunction

  小黄继续问道:“为啥传入的是一个流呢?”

  老罗不慌不忙地打开源码部分给她看:

    <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

  这里的形参比较难看懂,我们拆分一下:

代码含义
<R> Stream<R>泛型返回值,更好适配
flatMap
Function函数接口,接收一个对象,返回一个对象
? super TT的超类(包括T自身),一般来说是传入T类型的对象
? extends Stream<? extends R><? extends R> R或者R的子类,然后又在前面拓展这个流的适应能力,流的子类也可以
mapper参数名称,告诉用户是在进行映射

   这里不太适应的地方,就在于 Function 里类型上界和类型下界的运用。

4.【拓展】类型上界与类型下界

   这一部分需要自己思考一下,尤其是 <? super T> 的部分,下界通配符给出一个下界,但是它的子类也属于它,所以也可以加进去的。

   由于这个文章写得很棒,所以我不打算再写一篇了。为了防止链接丢失,我把最核心的内容放在下面:

上界通配符(Upper Bounds Wildcards)

下界通配符(Lower Bounds Wildcards)

PECS(Producer Extends Consumer Super)原则

  • 频繁往外读取内容的,适合用上界Extends。这里的读取,指的是对List之类的容器而言。
  • 经常往里插入的,适合用下界Super。这里的插入,指的是对List之类的容器而言。

由于这里经常有人问PECS该如何理解,我把Stack Overflow上的经典帖子也附上: What is PECS (Producer Extends Consumer Super)?

它的主要内容如下:

"PECS" is from the collection's point of view. If you are only pulling items from a generic collection, it is a producer and you should use extends; if you are only stuffing items in, it is a consumer and you should use super. If you do both with the same collection, you shouldn't use either extends or super.

Case 1: You want to go through the collection and do things with each item.
Then the list is a producer, so you should use a Collection<? extends Thing>

Case 2: You want to add things to the collection.
Then the list is a consumer, so you should use a Collection<? super Thing>.

   老罗正讲着……突然,HR叫她:“老罗!你的专属生日会,快来一趟。” 老罗一怔,五味杂陈,她对着小黄说:“要努力学习啊,公司的未来就靠你们年轻人了。”

  说着,老罗戴上耳机,一首《Рiчка》伴随她,踏上新的征程。

结语

  本例中,我们介绍了流处理中的 flatMap 的部分,它在我们使用 Stream 常常遇到的困难(得到了几段答案,要拼在一起)的解决方法。

  你还想到了什么呢?欢迎你在评论区和其他读者讨论!