使用java流按相同类型上的2个键分组

xienkqul  于 2023-01-11  发布在  Java
关注(0)|答案(7)|浏览(140)

使用java流,如何从一个列表创建一个Map来索引同一个类上的2个键?
我在这里给予了一个代码示例,我想Map“personByName”获得所有人的名字或姓氏,所以我想得到3个“史蒂夫”:当它是他们的名字或姓氏。我不知道如何混合2 Collectors.groupingBy。

public static class Person {
    final String firstName;
    final String lastName;

    protected Person(String firstName, String lastName) {
        super();
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

}

@Test
public void testStream() {
    List<Person> persons = Arrays.asList(
            new Person("Bill", "Gates"),
            new Person("Bill", "Steve"),
            new Person("Steve", "Jobs"),
            new Person("Steve", "Wozniac"));

    Map<String, Set<Person>> personByFirstName = persons.stream().collect(Collectors.groupingBy(Person::getFirstName, Collectors.toSet()));
    Map<String, Set<Person>> personByLastName = persons.stream().collect(Collectors.groupingBy(Person::getLastName, Collectors.toSet()));

    Map<String, Set<Person>> personByName = persons.stream().collect(Collectors.groupingBy(Person::getLastName, Collectors.toSet()));// This is wrong, I want bot first and last name

    Assert.assertEquals("we should search by firstName AND lastName", 3, personByName.get("Steve").size()); // This fails

}

我通过在2个Map上循环找到了一个变通方案,但它不是面向流的。

8ljdwjyq

8ljdwjyq1#

你可以这样做:

Map<String, Set<Person>> personByName = persons.stream()
       .flatMap(p -> Stream.of(new SimpleEntry<>(p.getFirstName(), p),
                               new SimpleEntry<>(p.getLastName(), p)))
       .collect(Collectors.groupingBy(SimpleEntry::getKey,
                   Collectors.mapping(SimpleEntry::getValue, Collectors.toSet())));

假设您向Person类添加了一个toString()方法,则可以使用以下命令查看结果:

List<Person> persons = Arrays.asList(
        new Person("Bill", "Gates"),
        new Person("Bill", "Steve"),
        new Person("Steve", "Jobs"),
        new Person("Steve", "Wozniac"));

// code above here

personByName.entrySet().forEach(System.out::println);
  • 产出 *
Steve=[Steve Wozniac, Bill Steve, Steve Jobs]
Jobs=[Steve Jobs]
Bill=[Bill Steve, Bill Gates]
Wozniac=[Steve Wozniac]
Gates=[Bill Gates]
r1wp621o

r1wp621o2#

例如,您可以合并两个Map<String, Set<Person>>

Map<String, Set<Person>> personByFirstName = 
                            persons.stream()
                                   .collect(Collectors.groupingBy(
                                                   Person::getFirstName, 
                                                   Collectors.toCollection(HashSet::new))
                                           );

persons.stream()
       .collect(Collectors.groupingBy(Person::getLastName, Collectors.toSet()))
       .forEach((str, set) -> personByFirstName.merge(str, set, (s1, s2) -> { 
            s1.addAll(s2); 
            return s1;
        }));

// personByFirstName contains now all personByName
tkqqtvp1

tkqqtvp13#

一种方法是使用最新的JDK12的Collector.teeing

Map<String, List<Person>> result = persons.stream()
       .collect(Collectors.teeing(
                Collectors.groupingBy(Person::getFirstName, 
                                      Collectors.toCollection(ArrayList::new)),
                Collectors.groupingBy(Person::getLastName),
                (byFirst, byLast) -> { 
                    byLast.forEach((last, peopleList) -> 
                           byFirst.computeIfAbsent(last, k -> new ArrayList<>())
                                  .addAll(peopleList));
                    return byFirst; 
                }));

Collectors.teeing收集到两个独立的收集器,然后将结果合并为一个最终值。
返回由两个下游收集器组合而成的收集器。传递到结果收集器的每个元素都由这两个下游收集器处理,然后使用指定的合并函数将其结果合并为最终结果。
因此,上面的代码按名字收集到一个Map,也按姓氏收集到一个Map,然后通过迭代byLastMap并通过Map.computeIfAbsent方法将其每个条目合并到byFirstMap中,从而将两个Map合并为最终Map。
注意,为了简化示例,我将集合Map到了Map<String, List<Person>>而不是Map<String, Set<Person>>,如果你真的需要一个集合Map,你可以这样做:

Map<String, Set<Person>> result = persons.stream().
       .collect(Collectors.teeing(
                Collectors.groupingBy(Person::getFirstName, 
                                      Collectors.toCollection(LinkedHashSet::new)),
                Collectors.groupingBy(Person::getLastName, Collectors.toSet()),
                (byFirst, byLast) -> { 
                    byLast.forEach((last, peopleSet) -> 
                           byFirst.computeIfAbsent(last, k -> new LinkedHashSet<>())
                                  .addAll(peopleSet));
                    return byFirst; 
                }));

请记住,如果需要将Set<Person>作为Map的值,则Person类必须实现hashCodeequals方法 * 一致 *。

6gpjuf90

6gpjuf904#

如果您想要一个真实的的面向流的解决方案,请确保不生成任何大型中间集合,否则流的大部分意义将丢失。
如果您只想过滤所有史蒂夫,请先过滤,后收集:

persons.stream
  .filter(p -> p.getFirstName().equals('Steve') || p.getLastName.equals('Steve'))
  .collect(toList());

如果你想用一个流元素做一些复杂的事情,例如把一个元素放到多个集合中,或者放在一个Map中的几个键下,只需使用forEach消费一个流,并在其中写入你想要的任何处理逻辑。

kcwpcxri

kcwpcxri5#

不能按多个值设置Map的键。对于要实现的目标,有三个选项:
1.将“personByFirstName”和“personByLastName”Map结合起来,您将得到重复的值(例如,Bill Gates将出现在Bill键下的Map中,也将出现在Gates键下的Map中)。@Andreas的答案给出了一个很好的基于流的方法来做到这一点。
1.使用像lucene这样的索引库,按名字和姓氏索引所有Person对象。
1.流方法-它在大型数据集上性能不佳,但您可以对集合进行流处理,并使用filter来获取匹配项:

persons
    .stream()
    .filter(p -> p.getFirstName().equals("Steve") 
         || p.getLastName().equals("Steve"))
    .collect(Collectors.asList());

(我已经根据记忆编写了语法,所以您可能需要调整它)。

t9aqgxwy

t9aqgxwy6#

如果我没弄错的话,你需要Map每个Person两次,一次是名字,一次是姓氏。要做到这一点,你必须加倍你的流。假设Couple是某个现有的2元组(Guava或Vavr有一些很好的实现),你可以:

persons.stream()
    .map(p -> new Couple(new Couple(p.firstName, p), new Couple(p.lastName, p)))
    .flatMap(c -> Stream.of(c.left, c.right)) // Stream of Couple(String, Person)
    .map(c -> new Couple(c.left, Arrays.asList(c.right)))
    .collect(Collectors.toMap(Couple::getLeft, Couple::getRight, Collection::addAll));

我没有测试过,但概念是:为每个人创建一个(name,person),(surname,person)...的流,然后简单地Map每对夫妇的左边值。asList将有一个集合作为值。如果你需要一个Set chenge最后一行.collect(Collectors.toMap(Couple::getLeft, c -> new HashSet(c.getRight), Collection::addAll))

wpx232ag

wpx232ag7#

尝试SetMultimap,从Google Guava或我的库abacus-common

SetMultimap<String, Person> result = Multimaps.newSetMultimap(new HashMap<>(), () -> new HashSet<>()); // by Google Guava.
// Or result = N.newSetMultimap(); // By Abacus-Util
persons.forEach(p -> {
     result.put(p.getFirstName(), p);
     result.put(p.getLastName(), p);
  });

相关问题