给定两个对象列表,我将能够根据其中一个属性判断哪些项目不在其交叉点中。我们来看下面的例子:
我有一个类Foo
有两个属性:boo
和placeholder
class Foo {
private int boo;
private int placeholder = 1;
public Foo(int boo) {
this.boo = boo;
}
public int getBoo() {
return boo;
}
}
现在我正在创建两个列表(让我们说这是我的输入)
List<Foo> list1 = new ArrayList<Foo>();
list1.add(new Foo(1));
list1.add(new Foo(2));
list1.add(new Foo(3));
List<Foo> list2 = new ArrayList<Foo>();
list2.add(new Foo(0));
list2.add(new Foo(1));
list2.add(new Foo(2));
现在我想说明哪些物品在list1
而不是list2
或list2
而不是list1
基于它们的属性boo
。所以在上面的例子中,我想要一个包含一个List<Foo> notInIntersectList
和一个Foo(0)
的Foo(3)
。
List<Foo> notInIntersectList = new ArrayList<Foo>();
list1.forEach(li1foo -> {
boolean inBothLists = false;
list2.forEach(li2foo -> {
if (li1foo.getBoo() == li2foo.getBoo()) {
inBothLists = true;
}
});
if (!inBothLists) {
notInIntersectList.add(li1foo);
}
});
//now I covered all items in list1 but not in list2. Now do this again with lists swapped, so I can also cover those.
//...
可悲的是,我将Local variable inBothLists defined in an enclosing scope must be final or effectively final
视为错误。这个问题是如何正确解决的,因为这似乎不是“正确的”解决方案?
你不能改变lambda表达式中的变量(参见:Variable used in lambda expression should be final or effectively final)
这是一种修复代码的方法(Streams很有趣)
List<Foo> notInIntersectList = list1.stream()
.filter(fooElementFromList1 -> list2
.stream()
.noneMatch(fooElementFromList2 -> fooElementFromList2.getBoo() == fooElementFromList1.getBoo()))
.collect(Collectors.toCollection(ArrayList::new));
list2.stream()
.filter(fooElementFromList2 -> list1
.stream()
.noneMatch(fooElementFromList1 -> fooElementFromList1.getBoo() == fooElementFromList2.getBoo()))
.forEach(notInIntersectList::add);
其复杂性是O(n*m)
(其中n
和m
分别是list1和list2中元素的数量)。
要在O(n+m)
中执行此操作,您可以使用Set。为此,你需要在equals
类上使用hashcode
和Foo
方法。这假设两个Foo
实例仅基于实例变量boo
的值相等。
class Foo {
....
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Foo other = (Foo) obj;
return boo == other.boo;
}
@Override
public int hashCode() {
return boo;
}
}
并使用Set
作为
Set<Foo> fooSet1 = new HashSet<>(list1);
Set<Foo> fooSet2 = new HashSet<>(list2);
fooSet1.removeAll(list2);
fooSet2.removeAll(list1);
List<Foo> notInIntersectList = Stream.concat(fooSet1.stream(), fooSet2.stream())
.collect(Collectors.toList());
如果你不能在你的班级上创建一个equals
和hashCode
(也许他们已经有了但不是基于boo
),我会做的是:
Set<Integer>
值的BitSet
(或boo
)。称之为set1
Set<Integer>
值的BitSet
(或boo
)。称之为set2
set1.retainAll(set2)
获取两组的交集。Stream.concat(list1.stream(),list2.stream())
.filter(item-> ! set1.contains(item.getBoo()))
.collect(Collectors.toList);
这是O(m + n)并且还确保维护原始列表中的项目的顺序,以及任何重复项目(具有相同boo
的项目)。
首先,你应该将方法equals
和hashCode
添加到你的班级Foo
(参见Why do I need to override the equals and hashCode methods in Java?)
class Foo {
private int boo;
private int placeholder = 1;
public Foo(int boo) {
this.boo = boo;
}
public int getBoo() {
return boo;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + boo;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof Foo))
return false;
Foo other = (Foo) obj;
return boo == other.boo;
}
}
现在你可以使用removeAll
的List
方法:
删除同样包含在指定集合中的所有此集合的元素(可选操作)。返回此调用后,此集合将不包含与指定集合相同的元素。
你将不得不建立一个新的List notInIntersectList
:
List<Foo> listIntersection = new ArrayList<>(list1);
listIntersection.removeAll(list2);
List<Foo> notInIntersectList = new ArrayList<>(list1);
notInIntersectList.addAll(list2);
notInIntersectList.removeAll(listIntersection);
它们使代码更难阅读,效率更低。
如果你只是使用传统的for
循环,你的代码应该工作......另外,你可以使用break
来停止搜索第二个匹配。
List<Foo> notInIntersectList = new ArrayList<Foo>();
for(Foo li1foo : list1) {
boolean inBothLists = false;
for(Foo li2foo : list2) {
if (li1foo.getBoo() == li2foo.getBoo()) {
inBothLists = true;
}
}
if (!inBothLists) {
notInIntersectList.add(li1foo);
}
}
你可能仍然认识到你的代码......现在这里是带有命令式编码的专业版:
List<Foo> notInIntersectList = new ArrayList<Foo>();
nextfoo: for(Foo li1foo : list1) {
for(Foo li2foo : list2)
if (li1foo.getBoo() == li2foo.getBoo())
continue nextfoo;
notInIntersectList.add(li1foo);
}
这实际上有一个非常明确和明确的逻辑(并且它将使功能性粉丝因为有效的“goto”而呕吐)。如果list2很大,它仍然很慢,我不想改变你的算法。
已有一个图书馆:
Set<String> wordsWithPrimeLength = ImmutableSet.of("one", "two", "three", "six", "seven", "eight");
Set<String> primes = ImmutableSet.of("two", "three", "five", "seven");
SetView<String> intersection = Sets.intersection(primes, wordsWithPrimeLength); // contains "two", "three", "seven"
// I can use intersection as a Set directly, but copying it can be more efficient if I use it a lot.
return intersection.immutableCopy();
如果你使用difference(Set<E> set1, Set<?> set2)
而不是intersection(Set<E> set1, Set<?> set2)
,你将得到两者的差异。
使用ImmutableSet.copyOf(Collection<? extends E> elements)
,您可以创建一个Set。
它被称为番石榴,并提供许多收集操作:https://github.com/google/guava/wiki/CollectionUtilitiesExplained
API:https://google.github.io/guava/releases/19.0/api/docs/com/google/common/collect/ImmutableSet.html
我在这看到四种可能的解决方案
1)在@ Anony-Mousse的回答之后避免lambda表达
2)在类级别包含变量(不推荐使用,因为此布尔值适合本地使用):
public class Testing {
boolean inBothLists = true;
public static void main(String[] args) {
List<Foo> notInIntersectList = new ArrayList<Foo>();
list1.forEach(li1foo -> {
inBothLists = false;
list2.forEach(li2foo -> {
if (li1foo.getBoo() == li2foo.getBoo()) {
inBothLists = true;
}
});
if (!inBothLists) {
notInIntersectList.add(li1foo);
}
});
System.out.println("Intersected values:");
notInIntersectList.forEach(liInFoo -> {
System.out.println(liInFoo);
});
}
3)使用List中的contains方法(避免布尔值):
List<Foo> notInIntersectList = new ArrayList<Foo>();
list1.forEach(li1foo -> {
if (!list2.contains(li1foo))
notInIntersectList.add(li1foo);
});
4)Java8 Stream API(已经在另一个答案中提到并避免了布尔值):
List<Foo> notInIntersectList = new ArrayList<Foo>();
list1.forEach(li1foo -> {
Foo result = list2.stream()
.filter(li2foo -> li1foo.getBoo() == li2foo.getBoo() )
.findAny()
.orElse(null);
if (result == null)
notInIntersectList.add(li1foo);
});
我会选择1),3)或4)。我只保留2.)来展示一个使用lambda函数内部变量的例子。
更新:我在recent Baeldung post on how to find an Element in a List中找到了3)和4)选项