繁体   English   中英

有没有办法将此for循环改为使用Java流?

[英]Is there a way to turn this for loop to use Java stream instead?

Set<String> unique = new HashSet<>();
List<String> duplicates = new ArrayList<>();

for (Animal cat: animals) {
    if (!unique.add(cat.getName())) {
        duplicates.add(cat.getName());
    }
}
return duplicates;

我想知道是否有简化的方法? 我是Java流的新手,我尝试使用Map,但是我改用了传统的for循环。

我想知道是否有简化的方法? 一世

流方式可能不是您所需要的,您的代码实际上很简单。

由于多个中间操作(过滤,映射,归约,流收集的结果...),流允许从输入( Stream<Foo> )传递到结果( FooList<String> ...)。
每个流操作都依赖于下一个操作的返回流:
为简化起见,将给出一条链:

a-> b(使用a)-> c(使用b)-> d(使用c)-> e(使用d)

实际上,您的代码不能依靠这种逻辑,因为返回的List<String>不需要仅返回具有重复名称的列表,可以这样编写:

List<String> duplicates =     
    animals.stream()
           .collect(Collectors.groupingBy(Animal::getName))
           .entrySet().stream()
           .filter(e -> e.getValue().size() > 1)
           .map(Entry::getKey)
           .collect(Collectors.toList());

你想返回一个List ,这些发生的顺序每个重复appearition的。
这意味着您不映射Stream<Animal> -> Stream<String>

a-> b(使用a)

因为如果未在结果中添加动物名称,则需要过滤掉元素,但是没有将流设计为增量地填充结果。 所以你被困住了。


您可以编写此代码,但是正如所说的那样,这实际上并不是一种简化,但是它仍然没有应用相同的逻辑,因为dup名称的顺序与您的代码中的顺序不同:

List<String> duplicates = 
          animals.stream()
                 .collect(Collectors.groupingBy(Animal::getName, LinkedHashMap::new, Collectors.toList()))
                 .values().stream()
                 .flatMap(l-> l.stream().skip(1)) 
                 .map(Animal::getName)
                 .collect(Collectors.toList());

您是否要根据Animal名称提取重复的Animal列表的字符串名称? 尽管您的代码不涉及第一个发现的重复项,而是返回列表中n-1重复项所在的List,但它是:

Set<String> set = new HashSet<>();
List<String> names = animals.stream()
                            .map(cat -> cat.getName())      // Names to collect and compare
                            .filter(name -> !set.add(name)) // Collect duplicates
                            .collect(Collectors.toList());  // To the List

该解决方案基于您的for-loop并且执行相同的操作。 但是,Stream API的文档指出,构造应为非推断无状态的 -这意味着独立于可能更改状态的源。


这是有关Stream-API文档的另一种工作方式-但有点复杂:

List<String> names = animals.stream()
    .collect(Collectors.groupingBy(
         Animal::getName, 
         Collectors.counting()))            // Collects to Map <name, count>
    .entrySet()                             // Gets the entries
    .stream()                               // Stream of the entries
    .filter(entry -> entry.getValue() > 1)  // Filters the duplicates
    .map(entry -> Collections.nCopies(      // Creates n-1 copies of the key as same as the
        entry.getValue().intValue() - 1,    //   OP's sample consumes the first duplication
        entry.getKey()))                    //   and includes the remainin ones
    .flatMap(List::stream)                  // Flattens the structure
    .collect(Collectors.toList());          // Results in the List

两种方式均来自输入:

List<Animal> animals = Arrays.asList(
    new Animal("A"), new Animal("A"), new Animal("A"), 
    new Animal("B"), new Animal("B"), new Animal("C"));

以下输出(无序):

[A,B,A]

我不知道这是否可以被简化,但这是使用流的一种方法:

return animals.stream()
        .collect(Collectors.groupingBy(Animal::getName))
        .values()
        .stream()
        .flatMap(group -> group.stream().skip(1))
        .map(Animal::getName)
        .collect(Collectors.toList());

不要重新发明轮子,而要使用commns-collection.substract()之类的库。

// I have not tested the code, but I think you get the idea
Set<> unique = new HashSet(animals)
Collection<> dublicates = CollectionUtil.subtract(animals, unique)

总有一种方法-仍然不简单,但是要简短得多:

List<Animal> duplicates = animals.stream()
  .collect( Collectors.collectingAndThen( Collectors.groupingBy( Animal::getName ),
    map -> {
      map.entrySet().removeIf( e -> e.getValue().size() < 2 );
      return( map.values().stream().flatMap( List::stream ).collect( Collectors.toList() ) );
    } ) );

问题是“如何使用Streams?” 但我认为一个不错的答案是“流并不总是一种简化”。

检测重复项的问题很经典,并且有一种典型的处理方法:

  • 对集合进行排序。
  • 对其进行迭代。
  • 每个等于其前身的元素都是重复的。

因此,尽管它并不能真正回答问题,但是正确的做法是像这样:

List<Animal> animals =
        Arrays.asList(
                new Animal("Alice"), 
                new Animal("Alice"), 
                new Animal("Alice"), 
                new Animal("Bob"),
                new Animal("Charlie"), 
                new Animal("Bob"));

List<Animal> duplicates = new ArrayList<>();

animals.sort(Comparator.comparing(Animal::getName));
for (int i = 1; i < animals.size(); i++) {
    Animal current = animals.get(i);
    if (animals.get(i - 1).getName().equals(current.getName())
            //Bonus : Also compare to the previous-previous in order to avoid multiple duplicates
            && (i < 2 || !animals.get(i - 2).getName().equals(current.getName()))) {
        duplicates.add(current);
    }
}

duplicates.forEach(a -> System.out.println(a.getName()));

输出:

Bob
Alice

它可能不容易理解(取决于您的经验),但要比使用Stream创建一个中间HashMap要干净得多。

因此,要么这样做(为了提高性能),要么就这样做(为了提高可读性)。

暂无
暂无

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

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