我有List
个元素,其中没有可测量的顺序。它们的属性也很复杂,我不能简单地将它们插入到Set
中(因为不同的属性可能代表相同的元素)。
通过我的程序,我分析列表中的每个元素,并在此基础上添加其他元素(如构建图形并转到每个节点以添加其他路径和节点)。但是,他们添加的元素可能等同于List
中的其他元素。在这种情况下,它们不会被添加,并且等效元素的属性已更改(假设为计数器)。
我一直在使用此代码来查找是否存在等效状态:
public static State stateAlreadyExists(State current) {
for (State any : list) {
if (equivalencyMethod(any, current)) {
return any;
}
}
return null;
}
但是,这个代码尽管有O(n)
的复杂性,但对我的情况来说还不够。我添加的每个元素都将执行此代码,并且每个分析元素添加约sqrt(N)
个元素(因此,分析元素400正在创建20个新元素,例如)。为了提高性能,我使用了Java的并行流:
public static State stateAlreadyExists(State current) {
Optional<State> opt = list.parallelStream().filter(
any -> equivalencyMethod(any, current)).findFirst();
if (opt.isPresent()) {
return opt.get();
}
return null;
}
性能显着提高。 问题是这段代码并不是真正等效,因为我们在返回元素之前分析整个流。大多数情况下,等效元素位于列表的第一个 sqrt(N)
元素中,因此在第一个匹配时停止的方法会更好。
我知道streams有一种 noneFound()
方法。一找到匹配就会返回。但是,它返回boolean
,而不是元素本身。有没有办法使用这个或类似构建的方法来返回找到的第一个匹配项?
根据JavaDoc,findFirst():
返回此流的第一个元素
findAny():
选择流中的任何元素。这是为了在并行操作中实现最大性能。
因此,通过使用findAny()
调用,我的代码可以变得更加高效,因为顺序对我的问题并不重要,因为任何时候只有1个等效元素。
答案 0 :(得分:2)
findFirst
是一个短路终端操作(感谢Keppil)。
public static void main(String[] args) {
final AtomicInteger countNew = new AtomicInteger();
final AtomicInteger countDoStuff = new AtomicInteger();
class A {
A() { countNew.getAndIncrement(); }
public boolean doStuff() { return countDoStuff.getAndIncrement() % 3 == 2; }
}
Stream.generate(A::new).limit(20).filter(A::doStuff).findFirst();
System.out.println("Number of times an A was created: " + countNew);
System.out.println("Number of times doStuff was called: " + countDoStuff);
}
此代码将打印
创建A的次数:3
调用doStuff的次数:3
但不是
创建A的次数:20
调用doStuff的次数:20
甚至更少
创建A的次数:20
调用doStuff的次数:3
答案 1 :(得分:1)
我建议你采用另一种方法。尝试以某种方式制定您的等效标准,以便您可以创建一个散列函数,该函数可以显着减少您必须执行的成对等价检查的数量。对于散列函数,如果两个不相等的项具有相同的散列,则没有问题,唯一重要的是两个相等的项具有相同的散列。然后,您只需将元素存储在HashSet
中,这将为您完成繁重的工作,您只需要实现元素的equals(Object other)
和hashCode()
。
如果无法找到合适的哈希码,您仍然可以考虑是否能够排序您的对象,也就是说您可以制定一个比较函数,该函数可以告诉您任何两个对象的顺序对象(或它们是相等的)。然后你可以使用TreeSet
和你的自定义比较器,这也可以超快速地工作。