如何在比较两个 ArrayList 时使用 removeAll?

huangapple go评论60阅读模式
英文:

How do I use removeAll when comparing 2 Arraylist<String[]>?

问题

import java.util.ArrayList;
import java.util.Iterator;

public class Main {
    public static void main(String[] args) {
        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"});
        
        ArrayList<String[]> result = new ArrayList<String[]>(b);
        
        Iterator<String[]> iterator = result.iterator();
        while (iterator.hasNext()) {
            String[] itemB = iterator.next();
            for (String[] itemA : a) {
                if (itemA[0].equals(itemB[0]) && itemA[1].equals(itemB[1])) {
                    iterator.remove();
                    break;
                }
            }
        }
        
        for (String[] item : result) {
            System.out.println("[" + item[0] + "," + item[1] + "]");
        }
    }
}

请注意,上述代码是根据您提供的内容编写的,用于比较两个 ArrayList<String[]> 并获取其差异。为了实现这一目标,代码复制了列表 b 的内容,然后通过迭代和比较删除了与列表 a 中相同的元素。最终,代码将差异部分的内容打印输出。

英文:

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

        ArrayList&lt;String[]&gt; a = new ArrayList&lt;String[]&gt;();
ArrayList&lt;String[]&gt; b = new ArrayList&lt;String[]&gt;();
a.add(new String[] {&quot;1&quot;,&quot;1&quot;});
a.add(new String[] {&quot;2&quot;,&quot;2&quot;});
a.add(new String[] {&quot;3&quot;,&quot;3&quot;});
a.add(new String[] {&quot;4&quot;,&quot;4&quot;});
b.add(new String[] {&quot;1&quot;,&quot;1&quot;});
b.add(new String[] {&quot;2&quot;,&quot;2&quot;});
b.add(new String[] {&quot;3&quot;,&quot;4&quot;}); // I&#39;d like to compare both and remove **a** to get only this line!
b.add(new String[] {&quot;4&quot;,&quot;4&quot;});

I'd like an ouput as below:

[3,4]

Any tip how to get just this line?

答案1

得分: 3

String[]封装在一个实现了equals()hashCode()的类中。为了额外的好处,还实现toString(),这样可以轻松打印。

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

测试

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

输出

[[3, 4]]

如果你尝试打印一个List<String[]>,它会显示类似[[Ljava.lang.String;@5ca881b5]的内容,根本不显示字符串值。

英文:

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&lt;StringArray&gt; a = new ArrayList&lt;&gt;();
ArrayList&lt;StringArray&gt; b = new ArrayList&lt;&gt;();
a.add(new StringArray(&quot;1&quot;,&quot;1&quot;));
a.add(new StringArray(&quot;2&quot;,&quot;2&quot;));
a.add(new StringArray(&quot;3&quot;,&quot;3&quot;));
a.add(new StringArray(&quot;4&quot;,&quot;4&quot;));
b.add(new StringArray(&quot;1&quot;,&quot;1&quot;));
b.add(new StringArray(&quot;2&quot;,&quot;2&quot;));
b.add(new StringArray(&quot;3&quot;,&quot;4&quot;));
b.add(new StringArray(&quot;4&quot;,&quot;4&quot;));
b.removeAll(a);
System.out.println(b);

Output

[[3, 4]]

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

答案2

得分: 1

removeAll使用内容对象的equals方法。如果您想基于数组(String[])的内容而不是其标识进行比较,您需要使用Arrays.equals()

现在,我们可以有条件地从列表中移除一些内容:

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

然而,还有另一种解决方案:


为了使removeAll起作用,您需要将内容转换为可以进行结构相等性比较的内容,所以从String[]到保留顺序的自然转换是将其转换为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());
// 非对称差异
b2.removeAll(a2);
System.out.println(b2);

// 如果您真的需要内部类型为String[]
final List<String[]> b3 = b2.stream()
                            .map(strings -> strings.toArray(String[]::new))
                            .collect(Collectors.toList());
System.out.println(b3);
英文:

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 -&gt; {
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&lt;List&lt;String&gt;&gt; a2 = a.stream().map(Arrays::asList).collect(Collectors.toList());
final List&lt;List&lt;String&gt;&gt; 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&lt;String[]&gt; b3 = b2.stream()
                            .map(strings -&gt; strings.toArray(String[]::new))
                            .collect(Collectors.toList());
System.out.println(b3);

答案3

得分: 1

你无法以直观的方式在 ArrayList<String[]> 中使用 replaceAll

  • List<T> API 指定 replaceAll 应使用 T.equals
  • 对于数组类型,equals 方法仅在目标对象和参数对象是同一对象时返回 true

解决方法:

如果你想在这种情况下使用 removeAll,你需要为 List 使用不同的元素类型。根据情况,你可以将元素设置为 ListSet 类型,泛型 Pair 类型或自定义类。基本要求是该类型提供一个按“值”比较的 equals 方法。

计算列表差异的其他替代方法包括使用带有迭代器的简单嵌套循环进行移除,或者根据 @Hawk 的回答使用 removeIf


上述所有方法的时间复杂度都为 O(MN),其中 MN 分别为两个列表的长度。

如果你要比较的列表可能很大,你可以创建一个临时集合,这样你就可以获得 O(MlogN)O(M) + O(N) 的时间复杂度;例如:

    Set<...> temp = new HashSet<>(a);  // 这一步的时间复杂度为 O(M)
b.removeAll(temp);                 // 这一步的时间复杂度为 O(N)

注意你需要考虑创建临时集合的成本,以及空间开销。


最后,如果任一列表中包含给定对的多个实例,则存在一个棘手的问题,即对其进行处理。是否可能存在重复项?如果没有,使用 Set 类来表示 ab 可能更好(更简单,更高效)。

英文:

You can't get replaceAll to work in an intuitive fashion with an ArrayList&lt;String[]&gt;:

  • The List&lt;T&gt; 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&lt;...&gt; temp = new HashSet&lt;&gt;(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.

huangapple
  • 本文由 发表于 2020年8月25日 20:33:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/63578946.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定