Java 8:如何比较Set中的所有元素

13

这可能是一个已经被问过的问题,但我找不到我需要的答案。

我有一个包含对象的Set,例如

public class MyObject {
    private LocalDate dateBeginning;
    private LocalDate dateEnd;

    public boolean overlap(MyObject otherDate) { /*code to check overlapping*/ }
}

我需要检查 Set 是否包含彼此重叠的两个元素。在“旧的Java”中,我会遍历集合两次,并检查所有可能的组合,然后在发现时中断或返回。

如何在Java 8中使用流和lambda完成此操作?

我已经尝试过 reduction()filter(),但似乎都不起作用。

.filter((obj1, obj2) -> { if (obj1.overlap(obj2)) return true;}) //doesn't work

虽然不是针对你的问题的答案,但也许会有所帮助。Guava的RangeSet可能是你正在寻找的 - ooxi
@Eran 我理解为OP希望得到一个包含与集合中任何其他元素重叠的所有元素的集合。 - Cubic
是的,@Eran,我想要一个布尔值返回。但是如果能得到一个重叠元素列表也可以。 - iberbeu
1
由于您正在处理 Set --因此顺序不重要--并且您没有以任何方式修改任何 Set,如果您的 Set 很大,您可能会发现使用 parallelStream() 有优势,这可以加快速度。 - dcsohl
请参考此答案:http://codereview.stackexchange.com/questions/30190/find-intersections-of-overlapping-intervals - krokodilko
3个回答

14

根据你在问题中提到的,一个可能的解决方案是两次循环遍历集合并确定是否存在任何重叠。因此我们需要确定的是,在集合中的任何元素中,是否可以找到任何其他不同的元素与之重叠。

使用Stream API,你可以这样实现:

boolean overlap = set.stream()
    .anyMatch(
        o1 -> set.stream().anyMatch(o2 -> o1 != o2 && o1.overlap(o2))
    );
anyMatch方法可以确定流中是否有满足给定条件的任何元素。因此,上面的代码是在询问是否存在一个o1,使得存在一个不同于o1(我们可以安全地使用!=,因为两个对象来自同一集合)且与之重叠的o2

请注意,这是一个O(n²)的实现:集合被遍历了两次。这可以在单次迭代中完成:每次迭代时,保留区间[dateBeginning,dateEnd]的并集; 如果当前区间和累积联合体之间的交集非空,则我们知道已经发生了重叠。


这是一个非常好的答案!首先,代码可以运行,其次,在一次迭代中完成它的可能性是一个非常好的观点。有没有使用流来实现这个替代方案的方法? - iberbeu
对于所有尝试此方法的人,请注意您不能两次使用相同的流:这意味着您需要像答案中所写的那样调用 set.stream() 两次。如果您直接在流上工作,它将无法正常工作。以下代码是错误的:Stream<> stream = set.stream(); boolean overlap = stream.anyMatch(o1 -> stream.anyMatch(o2 -> o1 != o2 && o1.overlap(o2))); - iberbeu
2
@iberbeu 是的,但你需要重新设计你的类,首先创建一个 DateInterval 类,该类将具有 unionintersect 方法。正如上面所评论的那样,你不能重复使用 Stream,它们只能使用一次。 - Tunaki
3
我会注意到,就像我在原始帖子中提到的那样,如果涉及的集合很大,parallelStream()可能是有利的。 - dcsohl

0

我还可以建议在这种情况下使用org.apache.commons.lang3.Range类和parallelStream以获得更好的性能。将其与Tunaki's solution相结合,我们得到:

Set<Range> ranges = new HashSet<>();
ranges.add(Range.between(LocalDate.of(2016, 5, 1), LocalDate.of(2016, 5, 5)));
ranges.add(Range.between(LocalDate.of(2016, 5, 3), LocalDate.of(2016, 5, 7)));

boolean overlap = ranges.parallelStream().anyMatch(
                o1 -> ranges.parallelStream()
                        .anyMatch(o2 -> o1 != o2 && o1.isOverlappedBy(o2))
);

System.out.println("overlap = " + overlap);

0

使用compareTo覆盖的想法实现。如果需要精确获取重叠范围或其数量,请使用此选项。

public class Range implements Comparable<Range> {
    private LocalDate startDate;
    private LocalDate endDate;

    public Range(LocalDate startDate, LocalDate endDate) {
        this.startDate = startDate;
        this.endDate = endDate;
    }

    @Override
    public int compareTo(Range range) {
        if (range.endDate.compareTo(endDate) >= 0 && range.startDate.compareTo(endDate) >= 0) return 1;
        if (range.endDate.compareTo(startDate) <= 0 && range.startDate.compareTo(startDate) <= 0) return -1;
        return 0;
    }
}

测试一下:

LocalDate May1 = LocalDate.of(2016, 5, 1);
LocalDate May3 = LocalDate.of(2016, 5, 3);
LocalDate May5 = LocalDate.of(2016, 5, 5);
LocalDate May7 = LocalDate.of(2016, 5, 7);
LocalDate May9 = LocalDate.of(2016, 5, 9);

Set<Range> ranges = new HashSet<>();

ranges.add(new Range(May1, May5));
ranges.add(new Range(May3, May7));
ranges.add(new Range(May7, May9));

Set filteredRanges = ranges.stream().collect(Collectors.toCollection(TreeSet::new));
long totalOverlaps = ranges.size() - filteredRanges.size();
System.out.println(totalOverlaps + " overlapping range(s)"); 

请注意,范围 { 1..3, 3..5 } 被认为是不重叠的。如果要将这种情况(当一个范围的endDate等于另一个范围的startDate时)视为重叠,请用<>替换<=>=

1
使用Comparable接口不是一个很好的想法。问题1:这不是区间的“自然”排序方式——对于类的用户来说,“更大”的范围意味着更长还是更晚并不明显。问题2:这个比较定义的顺序不是全序(例如,可能存在区间A、B和C满足A > B,A = C和B = C)。 - Hulk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接