如何检查一个字符串数组列表是否包含另一个字符串数组列表的子字符串?

6
List<String> actualList = Arrays.asList ("mother has chocolate", "father has dog");
List<String> expectedList = Arrays.asList ("mother", "father", "son", "daughter");

有没有一种方法可以检查expectedList是否包含actualList中字符串的任何子字符串?

我找到了一个嵌套的for-each解决方案:

public static boolean hasAny(List<String> actualList, List<String> expectedList) {
    for (String expected: expectedList)
        for (String actual: actualList)
            if (actual.contains(expected))
                return true;

    return false;
}

我试图寻找一个lambda解决方案,但是我没有找到。我发现所有的方法都是检查String#equals而不是String#contains

如果有类似以下的内容就好了:

CollectionsUtils.containsAny(actualList, exptectedList);

但它使用的是String#equals而不是String#contains来比较字符串。
编辑: 根据问题:如果实际列表中的所有子字符串都是期望列表的一部分,则我想要获得TRUE。 并且以下凯文的解决方案适合我。

我不确定Java,但在Kotlin中,您可以使用带有谓词的“any”。 - m0skit0
@JonnyHenly 没错,而且最高评分的答案并没有复制那个,这就是我的问题。 - Eugene
@Eugene 我在那篇最受欢迎的答案的评论区与作者讨论了这个问题,: ) - Jonny Henly
1
@JonnyHenly 我回答了,猜想你和我都是正确的... - Eugene
1
这个问题不是很具体。A) 找到一个匹配是否足够(如已实现); B) 必须为expectedList中的每个字符串找到一个匹配; C) actualList中的每个字符串必须包含在expectedList中的任意一个字符串中; D) BC 共同满足; E)??? - user85421
显示剩余4条评论
4个回答

11

这样怎么样:

list1.stream().allMatch(s1 -> list2.stream().anyMatch(s2 -> s1.contains(s2)))

在线试一下。

  • allMatch 将检查所有元素是否都为 true
  • anyMatch 将检查至少有一个元素为 true

这是 Java 7 风格的类似示例,没有使用 lambda 和 stream,以更好地理解其运行方式:

boolean allMatch = true;       // Start allMatch at true
for(String s1 : list1){
  boolean anyMatch = false;    // Start anyMatch at false inside the loop
  for(String s2 : list2){
    anyMatch = s1.contains(s2);// If any contains is true, anyMatch becomes true as well
    if(anyMatch)               // And stop the inner loop as soon as we've found a match
      break;
  }
  allMatch = anyMatch;         // If any anyMatch is false, allMatch becomes false as well
  if(!allMatch)                // And stop the outer loop as soon as we've found a mismatch
    break;
}
return allMatch;

如果你希望在代码中重复使用 CollectionsUtils.containsAny(list1, list2),并且希望其易于理解,你可以自己创建一个:


在线尝试

public final class CollectionsUtil{
  public static boolean containsAny(ArrayList<String> list1, ArrayList<String> list2){
    return list1.stream().allMatch(s1 -> list2.stream().anyMatch(s2 -> s1.contains(s2)));
    // Or the contents of the Java 7 check-method above if you prefer it
  }

  private CollectionsUtil(){
    // Util class, so it's not initializable
  }
}

然后您可以按照您想要的方式使用它:

boolean result = CollectionsUtils.containsAny(actualList, expectedList);

在线试用.


1
@JonnyHenly 是的,我也注意到了。嗯,如果 OP 还回复的话,我们就会看到这是否是他想要的。如果不是,修改起来应该很容易。 - Kevin Cruijssen
1
@KevinCruijssen,就我理解而言,这个问题的复杂度是二次方的... - Eugene
2
@Eugene 看来你是对的。我刚刚点赞了你的答案。与 O(n) 列表复杂度相比,使用 O(1) 集合复杂度的方法很好。正如我在上面的评论中与 Johny Henly 讨论过的那样,我不确定 OP 的意图是否还存在。到目前为止,他也没有回复评论,而他的原始代码已被其他人修改(尽管该原始片段的行为与当前的新片段有些相似)。基于他的原始代码,我确实认为你的答案应该得到勾选标记。 - Kevin Cruijssen
1
@NikolaJakubiak 这引发了严重的问题,这种方法现在非常令人困惑,您是否真的希望在找到单个字符串时返回true? :| 但是即使是这种情况,在我的答案中将allMatch更改为anyMatch也可以获得更快的方法;甚至是此答案的用户也同意,这将更有益。 - Eugene
1
@JonnyHenly,由于变量的类型为boolean,因此这些不是位运算符而是逻辑运算符。并且它们在这里已经过时了。如果你在anyMatch变成true之后立即break,那么在此之前它必须是false,因此你可以简单地使用anyMatch = s1.contains(s2); if(anyMatch) break;。同样,在外部循环中,你可以使用allMatch = anyMatch; if(!allMatch) break; - Holger
显示剩余7条评论

3

我99%确定您不是在寻找像此处最受欢迎的答案中的hasAny,而是想查看expectedList中的全部内容是否包含在actualList的任何字符串中。为此,首先创建一个Set并从该集合中操作会很有益(因为对于HashSetcontains的时间复杂度为O(1),而对于List则为O(n))。

现在考虑一下,既然您只需要用contains,那么您可以将该actualList拆分并从中创建唯一的单词:

private static boolean test(List<String> actualList, List<String> expectedList) {

    Pattern p = Pattern.compile("\\s+");

    Set<String> set = actualList.stream()
            .flatMap(p::splitAsStream)
            .collect(Collectors.toSet());

    return expectedList.stream().allMatch(set::contains);

}

1
你假设word匹配,根据给定的示例看起来很合理,但是OP询问了String::contains的语义,这是不同的。 - Holger

0
public static boolean containsAny(List<String> actualList, List<String> expectedList) {
    final Pattern words = Pattern.compile("\\s+");
    return actualList.stream()
                     .flatMap(words::splitAsStream)
                     .distinct()
//                     .allMatch(expectedList::contains)
                     .anyMatch(expectedList::contains);
}

1
足够接近了。有Pattern::splitAsStream,然后您应该收集到一个Set并调用contains,这样复杂度就会变成O(1) - Eugene
@Eugene 谢谢,我知道在 Pattern 中有这个方法。 - oleg.cherednik
但是现在...你不需要使用flatMap(Function.identity()),你可以使用flatMap(words::splitAsStream) - Eugene

0

Kevin的答案更好,但另一种方法是重写包装对象的equals方法。

import org.springframework.util.CollectionUtils;

class Holder {
    public String obj;

    public Holder(String obj) {
        this.obj = obj;
    }

    @Override
    public boolean equals(Object holder) {
        if (!(holder instanceof Holder))
            return false;

        Holder newH = ((Holder) holder);

        if (newH == null || newH.obj == null || obj == null)
            return false;

        return obj.contains(newH.obj) || newH.obj.contains(obj);  //actually it's should be one directed.
    }
}

CollectionUtils.containsAny(
            actual.stream().map(Holder::new).collect(Collectors.toList()),
            expected.stream().map(Holder::new).collect(Collectors.toList())
    );

注意:不应该是单向的 - a.equals(b) 应该与 b.equals(a) 的结果相同。 - user85421

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