如何有效地在双循环中执行条件检查?
How do I perform a conditional check in a double loop efficiently?
我真的不知道如何制定这个问题的标题,所以我直接举个例子。
假设我想遍历一个元素列表,并根据特定条件将所述元素添加到新列表中。
我在这里创建了一个方法,主要是想检查一个项目是否是第一个列表所独有的(不存在于第二个列表中)。现在我知道对于这个特别愚蠢的例子,你可以使用集合来解决这个问题,但我只是想说明这样的情况会弹出
public List<Item> newItems(List<Item> items, List<Item> otherItems) {
List<Item> newItems = new ArrayList<>();
for (Item i: items) {
for (Item j: otherItems) {
if (i.equals(j))
//Missing code
}
newItems.add(i);
}
return newItems;
}
所以这里我只想把当前的Item i
加到newItems
如果不等于otherItems
中的单项。我的第一个冲动是将 break;
放在 //Missing Code
的位置,但这只会打破第一个循环,而不会妨碍将 i
添加到 newItems
。
我知道一个正确的解决方案,您可以使用布尔变量来始终如一地检查 if 语句的真实性,然后根据它的真值将 Item i
添加到 newItems
第二个循环结束。它看起来像这样:
for (Item i: items) {
boolean check = true;
for (Item j: otherItems) {
if (i.equals(j))
check = false;
break; //To avoid unnecessary iterations
}
if (check)
newItems.add(i);
}
这看起来非常笨重,但也很多余。有没有更高效、更优雅的方式来做到这一点?
嗯,如果列表已经排序。我认为更快的解决方案是使用二进制搜索,因为它比顺序搜索更快。
for( Item i: items){
if(Collections.binarySearch(otherItems, i) < 0){
newItems.add(i);
}
}
如果必须,我会这样做:
for(Item i: items){
if(!otherItems.contains(i)){
newItems.add(i);
}
}
如果我对你的问题的理解正确,你需要创建一个列表,其中从 items
中收集的项目不包括 items
和 otherItems
中存在的项目。如果是,你可以简单地通过 List#removeAll()
:
public List<Item> newItems(List<Item> items, List<Item> otherItems) {
List<Item> res = new ArrayList<>(items); // create a copy of items
res.removeAll(otherItems); // remove items presented in otherItems
return res;
}
如果有其他条件排除项,使用流、过滤器和收集器,如下所示:
return items.stream()
.filter(i -> !otherItems.contains(i))
.filter( /* another condition */ )
.collect(Collectors.toList());
嗯,你可以做类似的事情;
CollectionUtils.removeAll(collection1, collections2);
这个方法returns也给你collection
在这种情况和其他情况下,我认为你可以给他们一个倒置的焦点,并假设所有项目都满足你的条件,然后删除那些不满足的条件。对于此示例:
public List<Item> newItems(List<Item> items, List<Item> otherItems) {
List<Item> newItems = new ArrayList<>(items);
for (Item i: items) {
for (Item j: otherItems) {
if (i.equals(j)){
newItems.remove(i)
break;
}
}
return newItems;
}
作为嵌套循环的替代方法,在 Java 8 中,您可以使用 Streams API 执行以下操作:
public List<Item> newItems(List<Item> items, List<Item> otherItems) {
return items.stream()
.filter(i -> !otherItems.contains(i))
.collect(Collectors.toList());
}
正如 and 所指出的,您可以使用 contains
或 binarySearch
来执行您描述的操作。既然你说了,这些操作只是示例性的,你可能有不同的条件——没有什么能阻止你使用相同的模式,但现在你可能必须自己编写特定的方法:
for(Item i: items) {
if(!hasMatchingCondition(i, otherItems) {
newItems.add(i);
}
}
static boolean hasMatchingCondition(Item i, List<Item> list) {
for(Item j: list) {
if(whatever condition regarding i and j) {
return true;
}
}
return false;
}
清洁和短路。
您可以使用标记语句在单个方法中执行相同的操作,即
outer: for(Item i: items) {
for(Item j: list) {
if(whatever condition regarding i and j) {
continue outer;
}
}
newItems.add(i);
}
但是一些开发人员认为带标签的语句是一种不受欢迎的功能,也许更重要的是,您可能会在某处找到 hasMatchingCondition
方法的另一种用途。
我真的不知道如何制定这个问题的标题,所以我直接举个例子。
假设我想遍历一个元素列表,并根据特定条件将所述元素添加到新列表中。
我在这里创建了一个方法,主要是想检查一个项目是否是第一个列表所独有的(不存在于第二个列表中)。现在我知道对于这个特别愚蠢的例子,你可以使用集合来解决这个问题,但我只是想说明这样的情况会弹出
public List<Item> newItems(List<Item> items, List<Item> otherItems) {
List<Item> newItems = new ArrayList<>();
for (Item i: items) {
for (Item j: otherItems) {
if (i.equals(j))
//Missing code
}
newItems.add(i);
}
return newItems;
}
所以这里我只想把当前的Item i
加到newItems
如果不等于otherItems
中的单项。我的第一个冲动是将 break;
放在 //Missing Code
的位置,但这只会打破第一个循环,而不会妨碍将 i
添加到 newItems
。
我知道一个正确的解决方案,您可以使用布尔变量来始终如一地检查 if 语句的真实性,然后根据它的真值将 Item i
添加到 newItems
第二个循环结束。它看起来像这样:
for (Item i: items) {
boolean check = true;
for (Item j: otherItems) {
if (i.equals(j))
check = false;
break; //To avoid unnecessary iterations
}
if (check)
newItems.add(i);
}
这看起来非常笨重,但也很多余。有没有更高效、更优雅的方式来做到这一点?
嗯,如果列表已经排序。我认为更快的解决方案是使用二进制搜索,因为它比顺序搜索更快。
for( Item i: items){
if(Collections.binarySearch(otherItems, i) < 0){
newItems.add(i);
}
}
如果必须,我会这样做:
for(Item i: items){
if(!otherItems.contains(i)){
newItems.add(i);
}
}
如果我对你的问题的理解正确,你需要创建一个列表,其中从 items
中收集的项目不包括 items
和 otherItems
中存在的项目。如果是,你可以简单地通过 List#removeAll()
:
public List<Item> newItems(List<Item> items, List<Item> otherItems) {
List<Item> res = new ArrayList<>(items); // create a copy of items
res.removeAll(otherItems); // remove items presented in otherItems
return res;
}
如果有其他条件排除项,使用流、过滤器和收集器,如下所示:
return items.stream()
.filter(i -> !otherItems.contains(i))
.filter( /* another condition */ )
.collect(Collectors.toList());
嗯,你可以做类似的事情;
CollectionUtils.removeAll(collection1, collections2);
这个方法returns也给你collection
在这种情况和其他情况下,我认为你可以给他们一个倒置的焦点,并假设所有项目都满足你的条件,然后删除那些不满足的条件。对于此示例:
public List<Item> newItems(List<Item> items, List<Item> otherItems) {
List<Item> newItems = new ArrayList<>(items);
for (Item i: items) {
for (Item j: otherItems) {
if (i.equals(j)){
newItems.remove(i)
break;
}
}
return newItems;
}
作为嵌套循环的替代方法,在 Java 8 中,您可以使用 Streams API 执行以下操作:
public List<Item> newItems(List<Item> items, List<Item> otherItems) {
return items.stream()
.filter(i -> !otherItems.contains(i))
.collect(Collectors.toList());
}
正如 contains
或 binarySearch
来执行您描述的操作。既然你说了,这些操作只是示例性的,你可能有不同的条件——没有什么能阻止你使用相同的模式,但现在你可能必须自己编写特定的方法:
for(Item i: items) {
if(!hasMatchingCondition(i, otherItems) {
newItems.add(i);
}
}
static boolean hasMatchingCondition(Item i, List<Item> list) {
for(Item j: list) {
if(whatever condition regarding i and j) {
return true;
}
}
return false;
}
清洁和短路。
您可以使用标记语句在单个方法中执行相同的操作,即
outer: for(Item i: items) {
for(Item j: list) {
if(whatever condition regarding i and j) {
continue outer;
}
}
newItems.add(i);
}
但是一些开发人员认为带标签的语句是一种不受欢迎的功能,也许更重要的是,您可能会在某处找到 hasMatchingCondition
方法的另一种用途。