Java: если объект с такими же параметрами существует, не добавляйте новый объект - PullRequest
1 голос
/ 07 октября 2010

Вот мой конструктор объектов

static class Edge {
    int source; // source node
    int destination; // destination node
    int weight; // weight of the edge
    int predecessor; // previous node
    public Edge() {};
    public Edge(int s, int d, int w) { source = s; destination = d; weight = w; }
}

Теперь вот инструкция, в которой я создаю новый объект Edge

edges.add(new Edge(nodeStart, tempDest, tempWeight));

Мне нужно пропустить этот оператор, если уже был создан объект с такими же параметрами (nodeStart, tempDest)

По сути, я не хочу добавлять одно и то же ребро дважды.

edges.add(new Edge(0, 1, tempWeight));
edges.add(new Edge(0, 1, tempWeight));

Если это произойдет, я хочу убедиться, что он добавляет его только один раз, а не новые идентичные объекты.

Ответы [ 6 ]

11 голосов
/ 07 октября 2010

Правильным способом было бы сделать edges a Set<Edge>.А затем правильно переопределить хэш-код и равно.

Итак, вы должны объявить edges следующим образом:

Set<Edge> egdes = new HashSet<Edge>();

И вы должны реализовать hashCode и равно как:

public boolean equals(Object o) {
    if (o == null) return false;
    if (o == this) return true;
    if (!(o instanceof Edge)) return false;
    Edge oEdge = (Edge) o;
    return this.source == oEdge.source && 
           this.destination == oEdge.destination && 
           this.weight == oEdge.weight;
}

public int hashCode(){
    return source * 3 + dest * 11 + weight * 13;
}

Вы должны прочитать о правильной реализации equals и hashCode.Мои примеры не велики.Но общая идея передана.

3 голосов
/ 07 октября 2010

Используйте Set вместо List для хранения ваших краевых объектов и правильного определения Edge equals и hashCode. Это предотвратит дубликаты, которые логически равны.

1 голос
/ 07 октября 2010

Это похоже на кандидата на фабрику, где вы можете проверить существующий объект по критериям внутри реализации фабрики и вернуть уже существующий объект ... только мои 2 цента ...

1 голос
/ 07 октября 2010

Основываясь на ваших комментариях, я думаю, что на самом деле все, что вам нужно, это настроенный ArrayList.

На дополнительном примечании , основанном на ваших комментариях к другим ответам, кажется, что вам нужно сделать много произвольного доступа по index , а не по параметру, То есть вы указали, что хотите получить Edge по порядку, в котором он был добавлен, а не указав уникальные параметры, и что вы можете захотеть получить доступ к этому порядку крайне непоследовательным образом. Для случая произвольного доступа производительность этой структуры данных будет лучше, чем для предложенного выше пользовательского набора, , поскольку платит высокую начальную стоимость за время вставки O (N), где N растет с размером набора; однако, когда вы делаете произвольный доступ , вы получаете такое же большое O (1) по индексу времени поиска, которое предлагает ArrayList. Для сравнения, для пользовательского набора, даже если вы расширяете LinkedHashSet для поддержания порядка доступа, вы платите O (1) время вставки, но O (N), чтобы перебирать записи по порядку, произвольный доступ время.

Обычно вы платите за ограничение уникальности и забываете об этом позже. В случае набора вы платите ограничение времени доступа позже. Конечно, это еще не конец истории, безусловно, есть решение, которое может удовлетворить оба мира. Предположим, что мы храним больше данных (ах, память по сравнению с процессором / временем), и в структуре данных ниже вместо поиска по списку, чтобы выяснить, есть ли у нас рассматриваемый Edge, мы также поддерживаем внутренний набор. Когда мы идем на вставку, мы сначала пытаемся найти край в наборе (O (1)), когда край не найден, мы добавляем его в набор и в список. Теперь нам нужно сохранить дополнительный Set (это стоимость памяти), чтобы избежать потери времени за просмотр нашего Списка во время вставок.

Пока все хорошо, но теперь нам нужно также определить хеш-функцию, которая генерирует уникальные хэш-коды для того, что вы считаете уникальным. Другими словами, мы хотим убедиться, в общих чертах: hashCode(Edge a) != hashCode(Edge b), где a != b должно удерживаться для некоторого невероятно большого пространства a и b, отличающихся в некотором смысле, который вы определяете. Учитывая, что вы определили такую ​​функцию, вы все же должны заплатить стоимость памяти для хранения набора, но, возможно, это не так уж плохо, если вы действительно заботитесь о стоимости вставки.

Вот простая концепция List, она легко модифицируется, чтобы также поддерживать Set, и вы бы изменили функцию contains ниже, чтобы проверять Set, а не проверять поиск в List, если вы хотите использовать гибридный подход.

public class EdgeArrayList extends ArrayList<Edge>{
    @Override
    public boolean add(Edge e){
        if(contains(e)){
            return false;
        }
        super.add(e);
        return true;
    }

    @Override
    public boolean contains(Object o){
        if(!(o instanceof Edge))
            return false;

        Edge e = (Edge) o;

        for(Edge item : this){
            if(item.source == e.source && 
               item.destination == e.destination && 
               item.weight == e.weight){
                return true;
            }
        }
       return false;
   }

}

Тогда использование будет таким, как вы предлагаете, где края, уже содержащиеся в EdgeArrayList, просто не будут добавлены:

EdgeArrayList foo = new EdgeArrayList();
foo.add(new Edge(0, 0, 0));
foo.add(new Edge(0, 0, 0));

В конце этого foo содержит только первый экземпляр Edge со всеми параметрами, установленными в 0 (я проверял это, это действительно работает).

Здесь есть некоторые подводные камни - проверка того, что новый Edge отсутствует в списке, приведет к попаданию O (N) в порядке размера текущего списка N .

1 голос
/ 07 октября 2010

Другой возможный способ - просто проверить edges.contains() перед добавлением нового ребра.

Edge newEdge = new Edge(nodeStart, tempDest, tempWeight);
if (!edges.contains(newEdge))
    edges.add(newEdge);

Вам нужно будет правильно реализовать метод equals в Edge, чтобы это работало. Смотрите мой другой ответ для этого кода.

Примечание: Этот способ гораздо медленнее, чем использование Set для исключения дубликатов.

1 голос
/ 07 октября 2010

Чтобы сказать, что вы не хотите "дубликатов", сначала вы должны сказать java, как вы считаете, что два ребра равны.Вы делаете это, используя метод equals (иначе у java нет способа узнать, когда два объекта вашего класса Edge равны).
Когда вы предоставляете метод equals для вашего объекта, вы должныдать hashCode вашему объекту.См. этот вопрос для объяснения.
(На самом деле вы oveerride эти методы. Реализации обоих по умолчанию присутствуют в java.lang.Object, из которого выходят все объекты Java)

Итак, сначала вы должны сказать Java, когда два края равны.Добавьте метод equals и метод hashcode к вашему классу.Подробности см. В ответе Джастина.

После этого вы должны убедиться, что в вашей коллекции краевых объектов нет дубликатов.Для этого вы можете использовать HashSet.(а не список. A List может иметь дубликаты. A Set не может. Этот учебник дает хорошее объяснение)

В утверждении edges.add..., edgesдолжен быть HashSet (или любой реализацией набора).
HashSet<Edge> edges = new HashSet<Edge>();

После того, как вы это сделаете, у вас будет список (er, Set) уникальных ребер.

Есливам нужно только список, а не набор, вы можете создать List из этого Set:

ArrayList edgesAsList = new ArrayList<Edge>(edges);

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...