【问题标题】:Use Java 8 lambdas to find a pair of objects in a list that meet a certain criteria使用 Java 8 lambda 在列表中查找满足特定条件的一对对象
【发布时间】:2014-08-16 01:31:30
【问题描述】:

给定A 类型的对象列表和boolean foo(A other); 方法,使用Java 8 lambda 查找任何对象对(例如a1.foo(a2) == true)的最佳方法是什么?

注意方法foo对称的,意思是a1.foo(a2) == a2.foo(a1)(如equals)。

另外,如果我只需要知道任何 a1 而不是 (a1, a2) 对,那么最好的方法是什么?

例如:

假设我有一个学生列表,我想在列表中找到与其他学生年龄相同的任何学生。

下面的方法Person.sameAgeAs(Person other)返回true/false,用于比较他们的年龄:

for (Person student1 : students) {
    for (Person student2 : students) {
        if (!student1.equals(student2)) { // If not the same student.
            if (student1.sameAgeAs(student2)) { // If they have the same age.
                return student1;
                }
            }
        }
    }

但我想用 lambdas 来做:

students.stream()...

编辑:

如果方法 foonot 对称的,那么 @srborlongan 下面的答案将是完美的。将其封装在 Pair 类中并接受 predicate

@NotNull
public static <I> Pair<I, I> findPair(@Nullable List<I> list, @NotNull BiPredicate<I, I> predicate) {
    if (list == null) return emptyPair();

    Optional<Pair<I,I>> item = list.parallelStream()
        .flatMap(item1 ->
            list.stream()
            .filter(item2 -> !item1.equals(item2))
            .filter(item2 -> predicate.test(item1, item2))
            .map(item2 -> new Pair<>(item1, item2))).findFirst();

    return item.orElse(emptyPair());
    }

但是,正如我所说,foo 是对称的,上面的代码效率低下,因为: (i) 如果存在答案,则可能会在所有非对称对之前搜索一些对称对; (ii) 如果不存在答案,即student1.sameAgeAs(student2) 永远不会返回true,此代码将搜索所有对,包括对称对。为了提高效率,代码应该只检查一半的对(因为student1.sameAgeAs(student2) == student2.sameAgeAs(student1))。

我最初的for (Person ... 示例并不好,因为它还会搜索所有对。这确实是应该被翻译成 lambdas 才能高效:

@NotNull
public static <I> Pair<I, I> findPair_Symmetric(List<I> list, BiPredicate<I, I> predicate_Symmetric) {
    if (list != null) {
        for (int i = 0; i < list.size(); i++) {
            for (int j = i + 1; j < list.size(); j++) {
                if ((i != j) && predicate_Symmetric.test(list.get(i), list.get(j))) {
                    return new Pair<>(list.get(i), list.get(j));
                    }
                }
            }
        }

    return emptyPair();
    }

【问题讨论】:

  • 这个算法是 O(n^2),但是如果你可以有一个比较函数而不是 BiPredicate,并且使用一个 TreeSet,那么它可以变成一个 O(nlogn)——而不是你只填充树,直到你发现它已经包含一个等价的对象。此外,如果您可以散列匹配的属性 - 您可以使用 HashSet 并获得 O(n) 性能。

标签: algorithm lambda java-8 java-stream


【解决方案1】:
//symmetric solution
@NotNull
public static <I> Pair<I, I> findPair_Symmetric(List<I> list, BiPredicate<I, I> predicate_Symmetric) {
  if (list != null) {
    // for (int i = 0; i < list.size(); i++)
    return IntStream.range(0, list.size())
      .mapToObj(
        // for (int j = i + 1; j < list.size(); j++)
        i -> IntStream.range(i + 1, list.size())
          // if (predicate_Symmetric.test(list.get(i), list.get(j))
          .filter(j -> predicate_Symmetric.test(list.get(i), list.get(j)))
          // return new Pair<>(list.get(i), list.get(j));
          .mapToObj(j -> new Pair<>(list.get(i), list.get(j)))
      )
      .flatMap(Function.identity())
      .findFirst()
      .orElseGet(() -> emptyPair())
    ;
  }

  return emptyPair();
}

// to get a1 only

students.stream()
  // Scala-ers (and other functional programming people) usually recommend
  // flatMap to solve life's problems.
  // (Except world peace. World peace is hard.)
  .flatMap(
    // for (Person student1 : students)
    student1 -> students.stream()
      // for (Person student2 : students)
      // if (!student1.equals(student2))
      .filter(student2 -> !student1.equals(student2))
      // if (student1.sameAgeAs(student2))
      .filter(student2 -> student1.sameAgeAs(student2))
      // or .filter(student1::sameAgeAs)
  )
  .findFirst()

// to get a1 and a2 (assuming the method makePair(Student, Student) exists)
// (makePair may return an array, a List, a Set, a Map.Entry, etc.)

students.stream()
  // Scala-ers (and other functional programming people) usually recommend
  // flatMap to solve life's problems.
  // (Except world peace. World peace is hard.)
  .flatMap(
    // for (Person student1 : students)
    student1 -> students.stream()
      // for (Person student2 : students)
      // if (!student1.equals(student2))
      .filter(student2 -> !student1.equals(student2))
      // if (student1.sameAgeAs(student2))
      .filter(student2 -> student1.sameAgeAs(student2))
      // or .filter(student1::sameAgeAs)
      .map(student2 -> makePair(student1, student2))
  )
  .findFirst()

【讨论】:

  • 如果 student1.sameAgeAs(student2) 永远不会返回 true 您的代码将搜索所有 (item1,item2) 对。但是您忽略了方法foo对称 的事实,因此为了提高效率,您应该只检查一半 对(因为student1.sameAgeAs(student2) == student2.sameAgeAs(student1))。
  • 添加了对称解。
  • 一个问题: 因此,如果我将外部流更改为 IntStream.range(0, lista.size()).parallel(),它应该是并行运行的,并且对于大型列表可能比我最初的 for 更快环形?跑得更快是练习的重点,因为 lambda 解决方案的可读性不如for 解决方案。
  • 对称解决方案有效。完美的。我只是要求您编辑它以从中删除 .filter(j -&gt; i != j) 行,因为它不是必需的:IntStream.range(i + 1, lista.size()) 已经不包括 j == i。
  • @marcg 先测试; Java 8 允许我们通过本质上以受限的顺序方式对流进行编码来创建可并行化的代码,然后我们可以使用 .unordered().parallel() 调用来装饰它以测试是否有性能提升。
猜你喜欢
  • 2020-05-20
  • 1970-01-01
  • 1970-01-01
  • 2023-01-04
  • 1970-01-01
  • 1970-01-01
  • 2021-08-07
  • 2014-08-29
  • 2021-05-15
相关资源
最近更新 更多