0

I need to compare two ArrayList<String[]> and get differences between them, so I thought using .removeAll as in ArrayList<String> but I don't know how to use it with ArrayList<String[]>. As example below:

        ArrayList<String[]> a = new ArrayList<String[]>();
        ArrayList<String[]> b = new ArrayList<String[]>();
        a.add(new String[] {"1","1"});
        a.add(new String[] {"2","2"});
        a.add(new String[] {"3","3"});
        a.add(new String[] {"4","4"});
        b.add(new String[] {"1","1"});
        b.add(new String[] {"2","2"});
        b.add(new String[] {"3","4"}); // I'd like to compare both and remove **a** to get only this line!
        b.add(new String[] {"4","4"});

I'd like an ouput as below:

[3,4]

Any tip how to get just this line?

Mario Codes
  • 689
  • 8
  • 15
Galla
  • 61
  • 1
  • 7
  • 1
    How is this question different to https://stackoverflow.com/questions/5283047/intersection-and-union-of-arraylists-in-java ? – jschnasse Aug 25 '20 at 12:46
  • 2
    @jschnasse I think because OP is working with `String[]` which don't have an overriden `equals()` nor `hashCode()` method. Which are used for the `removeAll()`, `containsAll()` and `retainAll()` method – Lino Aug 25 '20 at 12:57
  • Does this answer your question? [Intersection and union of ArrayLists in Java](https://stackoverflow.com/questions/5283047/intersection-and-union-of-arraylists-in-java) – vincrichaud Aug 25 '20 at 14:24

3 Answers3

3

Wrap the String[] in a class implementing equals() and hashCode(). For extra benefit, also implement toString(), so it's easy to print.

public final class StringArray {
    private final String[] arr;
    public StringArray(String... arr) {
        this.arr = arr.clone();
    }
    public String get(int index) {
        return this.arr[index];
    }
    @Override
    public int hashCode() {
        return Arrays.hashCode(this.arr);
    }
    @Override
    public boolean equals(Object obj) {
        if (! (obj instanceof StringArray))
            return false;
        StringArray that = (StringArray) obj;
        return Arrays.equals(this.arr, that.arr);
    }
    @Override
    public String toString() {
        return Arrays.toString(this.arr);
    }
}

Test

ArrayList<StringArray> a = new ArrayList<>();
ArrayList<StringArray> b = new ArrayList<>();
a.add(new StringArray("1","1"));
a.add(new StringArray("2","2"));
a.add(new StringArray("3","3"));
a.add(new StringArray("4","4"));
b.add(new StringArray("1","1"));
b.add(new StringArray("2","2"));
b.add(new StringArray("3","4"));
b.add(new StringArray("4","4"));

b.removeAll(a);
System.out.println(b);

Output

[[3, 4]]

If you had tried printing a List<String[]>, it would have shown something like [[Ljava.lang.String;@5ca881b5], not showing the string values at all.

Andreas
  • 154,647
  • 11
  • 152
  • 247
  • Thank you! It worked as I expected. I made few changes in my project and now it is working great. :) – Galla Aug 25 '20 at 19:47
1

removeAll uses equals of the content objects. If you want to compare 2 arrays (String[]) based on their content and not their identity, you need to use Arrays.equals().

Now we can remove some content form the list conditionally:

b.removeIf(bStrings -> {
    for (String[] aStrings : a) {
        if (Arrays.equals(aStrings, bStrings))
            return true;
    }
    return false;
})

However there is also another solution:


To make removeAll work, you need to convert the content to something, that can be compared for structural equality, so the natural conversion from a String[] to retain the order would be to make it a List.

final List<List<String>> a2 = a.stream().map(Arrays::asList).collect(Collectors.toList());
final List<List<String>> b2 = b.stream().map(Arrays::asList).collect(Collectors.toList());
// asymmetric difference
b2.removeAll(a2);
System.out.println(b2);

// if you really need the inner type to be String[]
final List<String[]> b3 = b2.stream()
                            .map(strings -> strings.toArray(String[]::new))
                            .collect(Collectors.toList());
System.out.println(b3);
Hawk
  • 2,042
  • 8
  • 16
1

You can't get replaceAll to work in an intuitive fashion with an ArrayList<String[]>:

  • The List<T> API specifies that replaceAll shall use T.equals.
  • The equals method for array types returns true if and only if the target and the argument object are the same object.

Solutions:

If you want to use removeAll in this, you need to use a different element type for the List. Depending on the situation, you could make the element a List or Set type, a generic Pair type or a custom class. The basic requirement is that the type provides an equals method that compares "by value".

The other alternatives for computing the list difference include using a simple nested loop with an iterator to do the removal or using removeIf as per @Hawk's answer.


All of the above will be O(MN) where M and N are the respective list lengths.

If the lists you are comparing are likely to be large, you could create a temporary set so that you can get O(MlogN) or O(M) + O(N) time complexity; e.g.

    Set<...> temp = new HashSet<>(a);  // This step is O(M)
    b.removeAll(temp);                 // This step is O(N) 

Note that you need to factor in the cost of creating the temporary set, AND the space overhead.


Finally, there is a tricky issue of what to do if either of the lists contain multiple instances of a given pair. Are duplicates possible? If not, it may be better (simpler, more performant) to use a Set class to represent a and b.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • Thanks for replying... nice information. BTW, duplicated isn't possible in my array. – Galla Aug 25 '20 at 19:48
  • So would it be better to represent them as `Set` instances? The way that you are "differencing" them won't notice differences in element order, so I surmise that is not significant. – Stephen C Aug 25 '20 at 23:17