Пользовательский компаратор TreeSet не работает с одинаковыми объектами - PullRequest
0 голосов
/ 13 мая 2018

У меня проблема с реализацией компаратора TreeSet. У меня есть простая игра, где животные ходят по доске, каждый ход они делают одно движение, если они по какой-то причине умирают, они помечаются как «мертвые», помещаются в список «DeadOrganisms» и впоследствии удаляются из очереди «treeset» "в этом фрагменте кода (я не могу удалить их сразу, потому что перебираю набор деревьев):

for(Organism org : DeadOrganisms){
    queue.remove(org);
}

Проблема в том, что некоторые из них вообще не удаляются, даже если в конце каждого хода они возвращаются в список DeadOrganisms из-за того, что они помечены как «мертвые». Будучи уверенным, что .remove вызывается каждый раз на мертвом организме, я почти уверен, что проблема заключается в классе Comparator:

class MyComparator implements Comparator<Organism> {
    @Override
    public int compare(Organism o1, Organism o2) {
        if (o1.getName().equals(o2.getName())) {
            return 0;
        }
        if (o1.getInitiative() > o2.getInitiative()) {
            return -1;
        } else if (o1.getInitiative() == o2.getInitiative()) {
            if (o1.getAge() > o2.getAge()) {
                return -1;
            } else {
                return 1;
            }
        } else {
            return 1;
        }
    }

}

Предполагается, что компаратор проверяет, совпадает ли имя o1 (уникальное для каждого символа на доске) с именем o2, а остальная часть кода предназначена для сортировки набора деревьев по инициативе или возрасту персонажа, если инициатива равна. Кусок кода для абстрактного класса Organism, из которого происходят все символы:

public abstract class Organism {
    protected int lastxpos;
    protected int lastypos;
    private final World myworld;
    private int strength;
    private int initiative;
    private int xPos;
    private int yPos;
    private int age;
    private String name;
    Color color;
    private boolean isdead;
    public Organism(World world, String name){
        this.name = name;
        this.color = Color.RED;
        this.strength = 0;
        this.initiative = 0;
        this.xPos = 0;
        this.yPos = 0;
        this.age = 0;
        this.isdead = false;
        this.myworld = world;
    }

Я знаю, что я делаю что-то не так или неправильно понимаю, как работают TreeSets (или оба), но я не могу понять, что. Я также знаю, что .remove

удаляет элемент e такой, что (o == null? E == null: o.equals (e))

Итак, в моем понимании, это роль

if (o1.getName().equals(o2.getName())) {
    return 0;
}

в моем классе Comparator, но, возможно, я что-то неправильно понимаю. Я был бы очень признателен за любую помощь с этим.

@ EDIT Я не знаю, имеет ли это значение, но до сих пор я тестирую его на одном типе животных, с одинаковым возрастом и инициацией, поэтому единственное различие между всеми животными - это их имя.

@ EDIT2 Я также заметил, что если удаляемый организм сначала находится в «очереди» древовидной структуры, то в методе сравнения после вызова queue.remove (org) «орг» никогда не сравнивается с первым объектом в древовидной структуре (он же). сам) только второй, третий и т. д.

@ EDIT3 Для пользователя NPE в комментариях: Объявление очереди:

public class World extends JPanel{
    *_declarations of some variables_*
    private final TreeSet<Organism> queue;

Инициализация очереди:

public World(int sizeX, int sizeY) {
        this.queue = new TreeSet<>(new MyComparator());
        *_ommitting rest of constructor code_*
    }

Объявление и инициализация DeadOrganisms:

    public void EndTurn(){
        List<Organism> DeadOrganisms = new ArrayList<>();
        *_omitting rest of the EndTurn code_*
     }

Ответы [ 2 ]

0 голосов
/ 13 мая 2018

Большое спасибо за помощь, к сожалению, проблема была в другом. В компараторе вы можете видеть, что если инициатива равна и возраст Organism1 НЕ больше, чем возраст Organism2, я бы просто поместил Organism1 после Organism2. Теперь допустим, что мои объекты в древовидной структуре - это A, B, C, D ..., X, Y, Z, их инициатива и возраст совпадают. Когда я добавляю их в свой набор деревьев, компаратор всегда возвращает 1, потому что инициатива и возраст совпадают, а это означает, что порядок набора деревьев всегда будет порядком вставки. Теперь, когда я хотел удалить объект H, метод .remove вводит набор деревьев A, B, C, D, ..., X, Y, Z, он начинается, например, с F (я не Не знаю, выберет ли он его случайно или это какой-то алгоритм) и вызовет компаратор с аргументами F и H, тогда компаратор вернет 1, как всегда в этом случае. Затем метод .remove «перепрыгнет» из F, например, к K, полностью пропустив H, он будет сравнивать K и H, сравнение снова вернет 1, сообщая методу .remove, что H возможно , возможно находится дальше в наборе деревьев, когда в действительности .remove уже пропустил мой объект H. Это приводит к тому, что .remove случайно не удаляет объект, потому что он пропустит его в своем алгоритме. Все, что мне нужно было сделать, это добавить статический счетчик к моим организмам. Теперь компаратор знает, что он ищет волка со значением счетчика 3, поэтому, если он перепрыгивает через него и находит волка со значением счетчика 6, он знает, что ему нужно вернуться назад.

TL; DR:

(TIL: алгоритм treeset не перемещается от одного узла к его «соседу», а скорее «перепрыгивает» через большие секции (они становятся меньше по мере приближения к нашему значению), Это означает, что если мы ищем 50 в нашем наборе {1,2 .... 100}, он (например) перепрыгнет к 20, затем к 80, затем к 40, затем к 55 и т. д.)

.remove будет перепрыгивать через мой объект в древовидной структуре из-за плохих компараторов «если».

0 голосов
/ 13 мая 2018

Из JavaDoc java.util.Comparator:

Следует соблюдать осторожность при использовании компаратора, способного наложить порядок, несовместимый с равенством, для порядка отсортированного набора (или отсортированной карты). Предположим, что отсортированный набор (или отсортированная карта) с явным компаратором c используется с элементами (или ключами), взятыми из набора S. Если порядок, наложенный c на S, не соответствует уравнениям, отсортированный набор (или отсортированная карта) будет вести себя "странно". В частности, отсортированный набор (или отсортированная карта) будет нарушать общий контракт для набора (или карты), который определяется в терминах равных.

... что звучит подозрительно очень похоже на то, что вы испытываете. Итак, давайте посмотрим на JavaDoc из java.util.Collection#remove:

Удаляет указанный элемент из этого набора, если он присутствует (необязательная операция). Более формально, удаляет элемент e такой, что (o==null ? e==null : o.equals(e)), если этот набор содержит такой элемент.

Так что Comparator здесь даже не вступает в игру. Поэтому решение состоит в том, чтобы реализовать методы equals и hashCode.

@Override
public int hashCode() {
    int hash = 3;
    hash = 97 * hash + this.initiative;
    hash = 97 * hash + this.age;
    hash = 97 * hash + Objects.hashCode(this.name);
    return hash;
}

@Override
public boolean equals(Object obj) {
    if (this == obj) {
        return true;
    }
    if (obj == null) {
        return false;
    }
    if (getClass() != obj.getClass()) {
        return false;
    }
    final Organism other = (Organism) obj;
    if (this.initiative != other.initiative) {
        return false;
    }
    if (this.age != other.age) {
        return false;
    }
    if (!Objects.equals(this.name, other.name)) {
        return false;
    }
    return true;
}

Примечание: оба метода были автоматически сгенерированы Netbeans.

...