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.

  • I'm not sure about Java but in Kotlin you have `any` that takes a predicate. – m0skit0 Sep 27 '18 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 – Eugene Sep 27 '18 at 13:39
  • @Eugene OP's original code resulted in true if only one match was found. – Jonny Henly Sep 27 '18 at 13:44
  • @JonnyHenly right, and the top rated answer does not replicate that thus my question – Eugene Sep 27 '18 at 13:45
  • @Eugene I addressed that with the author of the top rated answer in the comment's section of that answer, : ) – Jonny Henly Sep 27 '18 at 13:46
  • 1
    @JonnyHenly I answered, guessing me and you are right... – Eugene Sep 27 '18 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**) ??? – user85421 Sep 27 '18 at 13:55
  • I think that English might not be your first language, which is totally ok. But after your last edit to the question, it is still confusing as to what exactly you want. You say *" I want to get TRUE if ALL subStrings from `actualList` are part of `expectedList`."* Which doesn't quite make sense, since `actualList` contains longer strings and strings with space separated words. This means your example would not pass the check. I think you meant to say " I want to get TRUE if ALL subStrings from `expectedList` are part of `actualList`." Which would make sense, but I could still be wrong. – Jonny Henly Sep 28 '18 at 18:33
  • Could you please [edit] your question to include two examples, one in which the check passes and one in which the check fails. I believe these examples will serve to clear up any remaining confusion. – Jonny Henly Sep 28 '18 at 18:36

4 Answers4

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.

Kevin Cruijssen
  • 9,153
  • 9
  • 61
  • 135
  • Nice inclusion of bitwise assignment operators (can't remember what they're called), +1. Although, OP's original nested for-each loop code would have resulted in true if just one *match* was found between both lists. I guess that's up to OP to clarify though. – Jonny Henly Sep 27 '18 at 13:15
  • @JonnyHenly Ah ok, haven't looked very closely at OP's initial code to be completely honest, only at her explanation and test case. If it has to be as you say, then the outer loop/stream should be an `anyMatch` as well I guess. EDIT: In addition, OP's original code has been modified by someone else in the post (although behaves similar by the looks of it). – Kevin Cruijssen Sep 27 '18 at 13:19
  • I don't think it really matters, if it does I'm sure they'll leave a comment. The original code they posted didn't make much sense anyways, the loops contained a label (basically a goto statement) that wasn't needed and it kept iterating after the first match when they could have just returned true. EDIT: Yep I noticed that edit to the question, it appears to be the same with just the outer and inner loops switched but the conditional is the same. – Jonny Henly Sep 27 '18 at 13:26
  • 1
    @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. – Kevin Cruijssen Sep 27 '18 at 13:27
  • 1
    @KevinCruijssen this btw in my understanding has quadratic complexity... – Eugene Sep 27 '18 at 13:53
  • 2
    @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. – Kevin Cruijssen Sep 27 '18 at 14:01
  • THX, that's what I was looking for, simple oneliner and it works. @KevinCruijssen – Nikola Jakubiak Sep 27 '18 at 14:04
  • 1
    @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 – Eugene Sep 27 '18 at 14:08
  • I want to get TRUE if ALL Strings from actualList are part of expectedList. – Nikola Jakubiak Sep 27 '18 at 14:45
  • 1
    @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. – Holger Sep 28 '18 at 11:55
  • @Holger You're completely right about the operators being obsolete here.. Modified the code, and added the `break`s. – Kevin Cruijssen Sep 28 '18 at 12:30
  • @Holger you’re right, I just hadn’t seen those bitwise operators used in a while. As for breaking after one *match* I completely agree, I *think* I noted that in a comment to the question but I deleted it. – Jonny Henly Sep 28 '18 at 17:30
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);

}
Eugene
  • 117,005
  • 15
  • 201
  • 306
  • 1
    You are assuming *word* matches which looks reasonable with the given example, but the OP asked about `String::contains` semantic, which is different. – Holger Sep 28 '18 at 11:57
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);
}
Oleg Cherednik
  • 17,377
  • 4
  • 21
  • 35
  • 1
    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 Sep 27 '18 at 13:55
  • @Eugene thanks, I did know about this method in `Pattern`. – Oleg Cherednik Sep 27 '18 at 13:59
  • but now... you don't need `flatMap(Function.identity())` you could do `flatMap(words::splitAsStream)` – Eugene Sep 27 '18 at 14:02
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())
    );
DamienMiheev
  • 998
  • 8
  • 31