3

I have two Arraylist and I want to check if one is a subset of the other (ordering is not important in the comparison). The problem is: Lets say Ar1={e,e,r} and Ar2={e,r,b,d}. In my code it says Ar1 is a subset. But I want it to say false, cause Ar2 has only one e. How to do that?

public static void dostuff(String word1,String word2){
    List<String> list1 = new ArrayList<String>();
    List<String> list2 = new ArrayList<String>();

    for (String character : word1.split("")) {
        list1.add(character);
    }
    for (String character : word2.split("")) {
        list2.add(character);
    }

    boolean sub = list1.containsAll(list2) || list2.containsAll(list1);

    System.out.println(sub);
}
Andy Brown
  • 18,961
  • 3
  • 52
  • 62
johdoe
  • 77
  • 4
  • It is hard to tell exactly what you are doing. I'm assuming this is a theoretical example otherwise you could just do `boolean sub = word1.contains(word2) || word2.contains(word1)`? – Andy Brown Aug 02 '15 at 13:27
  • No that doesn not work, cause it looks at the order of the letters and i dont want te order to matter. – johdoe Aug 02 '15 at 13:48
  • that is fine, it would be worth editing your question to state that you don't want order to matter in the comparison. – Andy Brown Aug 02 '15 at 13:52
  • Check my answer, I believe it does all the work you want without having to call the method twice. – Shar1er80 Aug 02 '15 at 15:05

7 Answers7

3

I think this may be what you want. Note that list2.remove(elem) returns true if an element was removed, and false if not.

public static boolean dostuff(String word1,String word2){
    List<String> list1 = new ArrayList<>();
    List<String> list2 = new ArrayList<>();
    List<String> list3;

    for (String character : word1.split("")) {
        list1.add(character);
    }

    for (String character : word2.split("")) {
        list2.add(character);
    }

    list3 = new ArrayList<>(list2);

    boolean isSubset = true;

    for (final String elem : list1) {
        if (!list2.remove(elem)) {
            isSubset = false;
            break;
        }
    }

    if (isSubset) {
        return true;
    }

    for (final String elem : list3) {
        if (!list1.remove(elem)) {
            return false;
        }
    }

    return true;
}
marstran
  • 26,413
  • 5
  • 61
  • 67
  • To make this answer perfect, create a third list which is a copy of `list2`, and do the same loop inside `list3` against `list1`. OP wants to check both ways. – ttzn Aug 02 '15 at 13:24
  • Ah, he wants to check if either list1 is a subset of list2 OR if list2 is a subset of list1? – marstran Aug 02 '15 at 13:27
  • Indeed, look at his `||` expression. – ttzn Aug 02 '15 at 13:29
  • One more thing, your return mechanic is broken. Use a boolean flag for the first loop and return true if it passes, otherwise carry on to the second loop. – ttzn Aug 02 '15 at 13:58
  • Yes the 'or' is important. It does not matter even one is subset of the other or vica versa. – johdoe Aug 02 '15 at 14:19
  • Sorry @Amine. Did the change a bit too quickly. :) Fixed now. – marstran Aug 02 '15 at 14:41
0

Here is a Working Solution

Check Demo

 public static void main (String[] args) throws java.lang.Exception
 {
    dostuff("eer","erbd");
 }

 public static void dostuff(String word1, String word2) {
        List<String> list1 = new ArrayList<String>();

   for (String character : word1.split("")) {
            list1.add(character);
        }

        boolean sub = true;
        for (String character : word2.split("")) {
            if (list1.remove(character)) {
               if (list1.isEmpty()) {
                    break;
                }
            } else {
                sub = false;
                break;
            }
        }
        System.out.println(sub);
    }
rhitz
  • 1,892
  • 2
  • 21
  • 26
0

Note also that a mathematical, and java, set is unique, so be careful of using the term "subset".

You can use a frequency map to test if one list "has each element in another list, with the same or fewer occurrences". i.e. once you have your list you can convert it into a Map<T, Integer> to store the counts of each list element. The use of a map avoids mutating the original lists (which you would do if testing by removing elements from the master list as you encounter them):

public static <T> boolean isSublist(List<T> masterList, List<T> subList) {
    Map<T, Integer> masterMap = new HashMap<T, Integer>();
    for (T t : masterList) masterMap.put(t, 1 + masterMap.getOrDefault(t, 0));

    Map<T, Integer> testMap = new HashMap<T, Integer>();
    for (T t : subList) testMap.put(t, 1 + testMap.getOrDefault(t, 0));

    for(Map.Entry<T, Integer> entry : testMap.entrySet()) {
        if (masterMap.getOrDefault(entry.getKey(), 0) < entry.getValue()) return false;
    }

    return true;
}

getOrDefault is only available as of Java 8, but you can easily write your own method to take care of the same operation.

Andy Brown
  • 18,961
  • 3
  • 52
  • 62
  • 2
    word1.contains(word2) ? What if word1 = erbd and word2 = erd. They are subset but contains will return false. Please remove – rhitz Aug 02 '15 at 13:21
  • @Rohit. The original question did not specify if order was, or was not important - I included it as a (now answered) comment in my original question. Now removed, but the remainder of my answer is still valid. – Andy Brown Aug 02 '15 at 13:50
  • Yes this does not work i tried this. the order of things mather here. – johdoe Aug 02 '15 at 14:15
  • @johdoe - removed that part of the answer for you. The map method is fine. – Andy Brown Aug 02 '15 at 14:24
0

@Johdoe. The below logic may help you. you can optimize if you want.

ArrayList<String> list1 = new ArrayList<String>();
ArrayList<String> list2 = new ArrayList<String>();
list1.add("e");
list1.add("a");
list1.add("r");

list2.add("e");
list2.add("r");
list2.add("b");
list2.add("d");
list2.add("a");
System.out.println("list2 " + list2);
System.out.println("list1 " + list1);

Set<Integer> tempList = new HashSet<Integer>();

System.out.println("  containsAll " + list2.containsAll(list1));
for (int i = 0; i < list2.size(); i++) {
    for (int j = 0; j < list1.size(); j++) {
        if (list2.get(i).equals(list1.get(j))) {
            tempList.add(i);
        }
    }
}
System.out.println(" tempList  " + tempList);
System.out.println("list 1 is subset of list 2  "
        + (tempList.size() == list1.size()));
Shriram
  • 4,343
  • 8
  • 37
  • 64
0

Now that I understand that the order of the contents doesn't matter, you just want to know if all the characters of one string exists in another (with the same frequency) or vice versa.

Try this function, it'll check everything without having to call the method twice and without using streams:

public static boolean subsetExists(String s1, String s2) {
    String temp = s2.replaceAll(String.format("[^%s]", s1), "");
    char[] arr1 = s1.toCharArray();
    char[] arr2 = temp.toCharArray();
    Arrays.sort(arr1);
    Arrays.sort(arr2);

    boolean isSubset = new String(arr2).contains(new String(arr1));
    if (!isSubset) {
        temp = s1.replaceAll(String.format("[^%s]", s2), "");
        arr1 = temp.toCharArray();
        arr2 = s2.toCharArray();
        Arrays.sort(arr1);
        Arrays.sort(arr2);

        isSubset = new String(arr1).contains(new String(arr2));
    }
    return isSubset;
}

You don't have to bother turning your Strings into Lists. What's happening is we're checking if all the letters in s1 exist in s2 or vice versa.

We removed characters that are not in s1 from s2 and stored that result in a temporary String. Converted both the temporary String and s1 into char[]s. We then sort both arrays and convert them back into Strings. We then can check if NEW SORTED temporary String contains() the NEW SORTED s1. If this result is false, then we apply the same logical check from s2 to s1.

Usage:

public static void main(String[] args) throws Exception {
    String s1 = "eer";
    String s2 = "bderz";
    String s3 = "bderzzeee";

    System.out.println(subsetExists(s1, s2));
    System.out.println(subsetExists(s1, s3));
}

public static boolean subsetExists(String s1, String s2) {
    String temp = s2.replaceAll(String.format("[^%s]", s1), "");
    char[] arr1 = s1.toCharArray();
    char[] arr2 = temp.toCharArray();
    Arrays.sort(arr1);
    Arrays.sort(arr2);

    boolean isSubset = new String(arr2).contains(new String(arr1));
    if (!isSubset) {
        temp = s1.replaceAll(String.format("[^%s]", s2), "");
        arr1 = temp.toCharArray();
        arr2 = s2.toCharArray();
        Arrays.sort(arr1);
        Arrays.sort(arr2);

        isSubset = new String(arr1).contains(new String(arr2));
    }
    return isSubset;
}

Results:

false
true
Shar1er80
  • 9,001
  • 2
  • 20
  • 29
  • Sorry this fails on the following. if s1=abjkgh and s2=abk then s2 is not a subset of s1 in your code. but it should be. – johdoe Aug 02 '15 at 15:11
  • @johdoe I'm sorry, one of my original thoughts accounted for this. Give me a moment and I'll update. – Shar1er80 Aug 02 '15 at 15:21
0

I have found a solution myself, please check of this is right, but i believe it is.

public static void dostuff(String word1, String word2) {
    boolean sub = false;

    ArrayList<String> list1 = new ArrayList<String>();
    ArrayList<String> list2 = new ArrayList<String>();
    ArrayList<String> list3 = new ArrayList<String>();
    for (int i = 0; i < word1.length(); i++) {
        list1.add(word1.split("")[i]);
    }
    for (int i = 0; i < word2.length(); i++) {
        list2.add(word2.split("")[i]);
    }

    if (list1.size() >= list2.size()) {
        for (String i : list2) {
            if (list1.contains(i)) {
                list1.remove(i);
                list3.add(i);
            }
        }
        if (list2.containsAll(list3) && list2.size() == list3.size()) {
            sub = true;
        }
    } else if (list2.size() > list1.size()) {
        for (String i : list1) {
            if (list2.contains(i)) {
                list2.remove(i);
                list3.add(i);
            }
            if (list1.containsAll(list3) && list1.size() == list3.size()) {
                sub = true;
            }
        }
    }
    System.out.println(sub);
}
Shar1er80
  • 9,001
  • 2
  • 20
  • 29
johdoe
  • 77
  • 4
0

You could use a couple of maps to store the frequency of each letter:

public static void dostuff(String word1, String word2) {
    Map<String, Long> freq1 = Arrays.stream(word1.split("")).collect(
        Collectors.groupingBy(Function.identity(), Collectors.counting()));

    Map<String, Long> freq2 = Arrays.stream(word2.split("")).collect(
        Collectors.groupingBy(Function.identity(), Collectors.counting()));

    System.out.println(contains(freq1, freq2) || contains(freq2, freq1));
}

Where the contains method would be as follows:

private static boolean contains(Map<String, Long> freq1, Map<String, Long> freq2) {
    return freq1.entrySet().stream().allMatch(
        e1 -> e1.getValue().equals(freq2.get(e1.getKey())));
}

Test:

dostuff("eer", "erbd"); // {r=1, e=2}, {b=1, r=1, d=1, e=1}, false

dostuff("erbed", "eer"); // {b=1, r=1, d=1, e=2}, {r=1, e=2}, true

The idea is to use java 8 streams to create the frequencies map, and then, stream the entry set of both maps to compare all the elements and their frequencies. If all entries match, then it means that the second list contains all the elements of the first list with the same frequencies, regardless of the order.

In case the result is false for the first list, the check is performed the other way round as well, as per the question requirements.

fps
  • 33,623
  • 8
  • 55
  • 110