如何从由自定义对象组成的 ArrayList 的 ArrayList 中删除重复项

How to remove duplicates from an ArrayLlist of ArrayLists that are composed of custom objects

我有一个递归函数,它生成一个列表列表,用于跟踪纸牌游戏的有效手牌组合:

List<List<HandComponent>> validSCompArrangements = new ArrayList<>();

此列表由递归函数成功填充,但经常有重复的子列表(按内容而不是顺序),由于函数的要求性质,这是不可避免的。我希望删除这些重复的子列表条目 (List<\HandComponent>),以便上面的列表最终只包含内容唯一的子列表,因为顺序无关紧要。

这是HandComponent的重要部分class:

public class HandComponent {

    private Type mType;
    private Card mCard; // For runs this is the middle card
    private Source mSource;

    public HandComponent(Type type, Card card, Source source)
    {
        init(type, card, source);
    }

    public enum Type {PAIR, TRIPLE, QUAD, RUN}
    public enum Source {STOLEN, SECRET, EITHER}
...
}

只有当子列表包含完全相同的 HandComponents(即每个列表的组件之间的类型、卡片和来源必须相同)时,才应将其视为等同于另一个子列表。 Card 是另一个文件中定义的另一个枚举。

所以,如果 "validSCompArrangements" 中的两个列表是

(PAIR,CARD1,STOLEN), (TRIPLE,CARD7,STOLEN), (RUN, CARD8, SECRET)

(TRIPLE,CARD7,STOLEN), (RUN, CARD8, SECRET), (PAIR,CARD1, STOLEN)

它们应该被认为是相同的,因为它们最终包含相同的 HandComponents,即使顺序不同并且应该删除一个,以便 "validSCompArrangements" 只包含该唯一列表一次。

对此,我发现了有关如何解决此问题的点点滴滴,但没有发现具有这种列表列表与自定义对象组合的特征。 一种方法似乎是实现一个自定义 Comparator,它比较 HandComponent 实例以与 Collections 一起使用,以便对子列表进行排序,然后另一个自定义 Comparator 来比较这些已排序的子列表是否重复,尽管这看起来有点笨拙而且我'我不完全确定如何覆盖比较方法以及我需要为每个比较器期望什么样的 return 。我看到的唯一另一件事是,因为对于我的使用,子列表和主 "validSCompArrangements" 列表本身的顺序无关紧要,我应该使用 Sets 和 HashSet 来解决相反,这个问题,我不知道如何使用它们来解决这个问题,除了我可能需要覆盖我的 HandComponent class 的 hashCode 和 equals 方法之外,再次不确定如何这样做。

总的来说,我只是有点困惑,因为我能设法找到与此远程相关的任何示例通常只是在谈论一个包含基元而不是枚举的自定义对象列表,或者使用的列表列表只有基元,根本没有自定义对象。事实上,这是一个自定义对象列表的列表,其成员是枚举,这让我有点不知如何去做。

例如,这个问题中的标记答案:,它只解决了我的部分问题,尽管 OP 说它对我有用,但它似乎对我不起作用。 运行 该代码原样,除了更改

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

Set<List<Integer>> dedupedCollection = new HashSet<>();

正如 OP 所建议的那样,它产生了 3 个条目的集合,其中第二个条目 5、10、5 不被视为重复项并被忽略。

编辑:

到目前为止,我发现的最接近的事情是使用以下方法将我的顶级列表转换为 HashSet:

Set<List<HandComponent>> handSet = new HashSet<>(validSCompArrangments);

但这只会消除重复的列表,如果它们的顺序相同(我猜这是由于列表默认实现 "equals()" 的性质),而我需要它来考虑相同的列表内容不同,但顺序也不同。解决此问题的一种方法是对 HandComponent 子列表也使用 Sets,因为它们天生不关心顺序,但这将防止这些集合具有我确实需要允许的重复 HandComponents。

正如你所说,你只需要实施equals :)

我已经为您提供了如何在 HandComponent class 中实现 equals 方法,以及如何使用 HashSet 仅获取没有重复的组合。

我已经在Java8中实现了,如果你愿意,你也可以尝试使用for循环来改变它:)

这是`HandComponent

equals实现
public class HandComponent {

public enum Type {PAIR, TRIPLE, QUAD, RUN}

public enum Source {STOLEN, SECRET, EITHER}

public enum Card {ACE, ONE, TWO, TRHEE}

private Type type;
private Card card;
private Source source;

public HandComponent(Type type, Card card, Source source) {
    this.type = type;
    this.card = card;
    this.source = source;
}

@Override
public boolean equals(Object o) {
    if (this == o) {
        return true;
    }
    if (!(o instanceof HandComponent)) {
        return false;
    }
    HandComponent handComponent = (HandComponent) o;

    if (type != handComponent.type) {
        return false;
    }

    if (card != handComponent.card) {
        return false;
    }

    if (source != handComponent.source) {
        return false;
    }

    return true;
}

@Override
public String toString() {
    return "HandComponent=[" + String.join(", ", Arrays.asList(type.toString(), card.toString(), source.toString())) + "]";
}
}

您可以在下面看到如何使用它

public class Main {

public static void main(String[] args) {

    // Creating 2 hand components
    HandComponent handComponent1 = new HandComponent(HandComponent.Type.PAIR, HandComponent.Card.ACE, HandComponent.Source.STOLEN);
    HandComponent handComponent2 = new HandComponent(HandComponent.Type.QUAD, HandComponent.Card.TRHEE, HandComponent.Source.EITHER);

    // 2 combinations with the same card, but different order => they are the same
    List<HandComponent> firstCombination = Arrays.asList(handComponent1, handComponent2);
    List<HandComponent> secondCombination = Arrays.asList(handComponent2, handComponent1);

    // Mixing 2 combinations together
    List<List<HandComponent>> combinations = Arrays.asList(firstCombination, secondCombination);

    // printing the mix
    System.out.println("Before: " + combinations);

    // removing duplicates
    List<ArrayList<HandComponent>> collect = combinations.stream() // having a stream of list<HandComponent>
            .map(HashSet::new) // converting to HashSet, which mean there won't be duplicate in the combinations.
            .distinct()        // getting only the distinct combinations
            .map(ArrayList::new) // reconverting to array list
            .collect(Collectors.toList()); // collecting them as list

    // result without duplicates
    System.out.println("After: " + collect);

    // You can now implement it with loop and no java 8 :)
}
}

最终最适合我的是按照 Jiajie Xu 的建议为我的 HandComponent class 实现 "equals()" 方法,以及 "hashCode()" 自动生成的方法 Android Studio 使用上下文菜单中的选项或 Alt + Insert:

 @Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    HandComponent that = (HandComponent) o;
    return mType == that.mType &&
            mCard == that.mCard &&
            mSource == that.mSource;
}

@Override
public int hashCode() {

    return Objects.hash(mType, mCard, mSource);
}

然后我还让 class 实现了 Comparable 接口以便与 Collections class 一起使用,并在 "compareTo()" 方法中指定了 HandComponent 实例的排序优先级,如下所示:

 @Override
public int compareTo(@NonNull HandComponent other) {

    // Check Type first
    int compareResult = mType.compareTo(other.mType);
    if(compareResult == 0)
    {
        // Check Card second
        compareResult = mCard.compareTo(other.mCard);
        if(compareResult == 0)
        {
            // Check Source last
            compareResult = mSource.compareTo(other.mSource);
        }
    }

    return compareResult;
}

由于 List 的 Comparable 默认实现要求列表顺序相同以便 return "true" 在比较两个列表时,我需要每次都对列表​​列表进行排序删除重复项,这非常好,因为我后来从组织中受益。

最终,这允许我通过首先确保 HandComponent 的子列表都已排序然后创建顶级列表的 HashSet 来从我的自定义对象列表列表中删除重复项。

List<List<HandComponent>> unsortedList = new ArrayList<>();
... // Populate list

for(int i = 0; i < unsortedList.size(); i++)
{
    Collections.sort(unsortedList.get(i));
}

Set<List<HandComponent>> sortedDeDupedSet = new HashSet<>(unsortedList);

// Convert back to list since I need order to matter again later on
List<List<HandComponenet>> sortedDeDupedList = new ArrayList<>(sortedDeDupedSet);

现在我已经正确地实施了 "equals()" 和 "hashCode()" 方法,并且事先使用 "compareTo()" 对列表进行了排序,因此这正确地从顶级列表中删除了重复项利用 List 的默认 Comparable 实现。由于我被限制为 Java 7 必须使用 for 循环对列表本身进行排序确实感觉有点糟糕,但正如我之前所说最终将列表排序用于其他目的和很多与手动比较每个 List 条目所需的嵌套 for 循环相比,使用 HashSet 仍然可以节省时间和代码。