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

Is there a way to check if expectedList contains any substrings of the strings in actualList?

I found a nested for-each solution:

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;
}

I was trying to a find lambda solution, but I could not. All the methods I found check for String#equals and not for String#contains.

It would be nice to have something like:

CollectionsUtils.containsAny(actualList, exptectedList);

But it compares strings using String#equals not String#contains.

EDIT:

Based on questions: I want to get TRUE if ALL subStrings from actualList are part of expectedList. And solution from Kevin below works for me.

9
  • I'm not sure about Java but in Kotlin you have any that takes a predicate. Commented Sep 27, 2018 at 12:58
  • aren't you looking for all if they match? because your for loop will stop as soon as a single match is found Commented Sep 27, 2018 at 13:39
  • @Eugene OP's original code resulted in true if only one match was found. Commented Sep 27, 2018 at 13:44
  • 1
    @JonnyHenly I answered, guessing me and you are right... Commented Sep 27, 2018 at 13:51
  • 1
    The question is not very specific. A) is it enough to find one match (as implemented); B) must find a match for every string in expectedList; C) every string of actualList must contain one of the strings in expectedList; D) B and C together; E) ??? Commented Sep 27, 2018 at 13:55

4 Answers 4

11

How about something like this:

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

Try it online.

  • allMatch will check if everything is true
  • anyMatch will check if at least one is true

Here something similar in Java 7 style without lambdas and streams to understand a bit better what is going on:

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;

Try it online.


If you prefer to have a CollectionsUtils.containsAny(list1, list2) you can reuse elsewhere in your code, you could always make one yourself:

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
  }
}

Which can then be used as you wanted:

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

Try it online.

Sign up to request clarification or add additional context in comments.

12 Comments

@JonnyHenly Yeah, noticed that as well. Ah well, if OP still responses we'll see if this is what he intended. If not, it should be pretty easy to modify.
@KevinCruijssen this btw in my understanding has quadratic complexity...
@Eugene Seems you're indeed right. I've just upvoted your answer. Nice approach with the O(1) Set complexity in comparison to the O(n) List complexity. As I discussed with Johny Henly in the comments above, I'm not sure what OP's intentions are/aren't anymore.. He also doesn't respond to comments thus far, and his original code has been modified by someone else (although that original snippet is behaving somewhat similar as the current new snippet). Based on his original code, I indeed suspect your answer deserves the check-mark here.
@NikolaJakubiak this raises serious questions, this method is very confusing now, do you really want to return true in case a single string is found? :| But even if that is the case, simply change allMatch with anyMatch in my answer for a faster approach; even the user of this answer agrees, it would be more beneficial
@JonnyHenly since the variables have boolean type, these are not bitwise operators but logical operators. And they are obsolete here. If you break as soon as anyMatch becomes true, it must be false before that, hence you can simply use anyMatch = s1.contains(s2); if(anyMatch) break;. Likewise, you can use allMatch = anyMatch; if(!allMatch) break; in the outer loop.
|
3

I am 99% sure you are not looking for hasAny like the most upvoted answer here, but instead you want to see if all from expectedList are contained in any String in actualList. For that it would be beneficial to first create a Set and work of that (since contains is O(1) for HashSet and opposed to O(n) for List).

Think about it now, since all you want is contains, you can split that actualList and create unique words from that:

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 Comment

You are assuming word matches which looks reasonable with the given example, but the OP asked about String::contains semantic, which is different.
0

Kevin answer is better one, but another approach is to overriding the equals method of Wrapper object.

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())
    );

1 Comment

Note: no, it should not be one directed - a.equals(b) should result in the same as b.equals(a)
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);
}

3 Comments

close enough. there is Pattern::splitAsStream; than you should collect to a Set and call contains on it, so that complexity would become O(1) instead
@Eugene thanks, I did know about this method in Pattern.
but now... you don't need flatMap(Function.identity()) you could do flatMap(words::splitAsStream)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.