如何有效地在双循环中执行条件检查?

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 中收集的项目不包括 itemsotherItems 中存在的项目。如果是,你可以简单地通过 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

https://commons.apache.org/proper/commons-collections/javadocs/api-release/org/apache/commons/collections4/CollectionUtils.html

在这种情况和其他情况下,我认为你可以给他们一个倒置的焦点,并假设所有项目都满足你的条件,然后删除那些不满足的条件。对于此示例:

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 所指出的,您可以使用 containsbinarySearch 来执行您描述的操作。既然你说了,这些操作只是示例性的,你可能有不同的条件——没有什么能阻止你使用相同的模式,但现在你可能必须自己编写特定的方法:

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 方法的另一种用途。