views:

141

answers:

6

Here is my object constructor

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

Now, here is the statement where I create a new Edge object

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

I need to skip over that statement if there has already been an object created with the same parameters (nodeStart, tempDest)

Basically, I don't want to add the same edge twice.

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

If that happens, I want to make sure it only adds it once, and not new identical objects.

+11  A: 

The proper way would be to make edges a Set<Edge>. And then properly override hashcode and equals.

So, you should declare edges like so:

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

And you should implement hashCode and equals like so:

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

You should read up on properly implementing equals and hashCode. My examples are not great. But the general idea is conveyed.

jjnguy
I'm sorry, I really do not understand this at all.
Bobby S
@Bobby, well, lets fix that then! What type is `edges` in your code?
jjnguy
I think it used the Vector class -- Vector<Edge> edges = new Vector<Edge>();
Bobby S
@Bobby, well, the vector class allows for duplicate objects. In other words it will let two of the same edges be in it. The `Set` in my answer will only allow one of each edge in the set. So, if you call `add(new Edge())` and the edge with the same values has already been added, that new edge will not be added the the set. (Which is what you want)
jjnguy
With the Set, I cannot use .get() now. Correct? I need to get elements of my object. Like edges.get(j).source
Bobby S
@Bobby, correct. Instead, if you want to look at the items in the set, you need to iterate over them like so: `for (Edge edg: edges) {}`
jjnguy
@Bobby, then you can use `edg` inside of that `for` loop.
jjnguy
@Bobby, if you are doing graph stuff, there should never be a need for you to access an edge at a specific index in your set of edges. In other words, only being to iterate through the edges shouldn't be a problem.
jjnguy
@Justin I don't think that is going to work for me. I have this specific black box algorithm that using .get() and I need to leave that alone.Is there no other way to keep it in the Vector class?
Bobby S
Ok, well, I'm going to add another answer to your question then. Sorry.
jjnguy
@Bobby, are you indexing to get a specific edge? (that is, you say, I want edge X from the list?)
Mark E
Yes. Stuff like this... int newDistance = distance[edges.get(j).source] + edges.get(j).weight; if (newDistance < distance[edges.get(j).destination]) { distance[edges.get(j).destination] = newDistance; edges.get(edges.get(j).destination).predecessor = edges.get(j).source; }
Bobby S
Thanks for all your kind help, don't think it's not appreciated but, another thing, what is this line: if (!(o instance of Edge)) return false;
Bobby S
@Bobby, basically, if `o` (the object passed into the equals method) is not an instance of an `Edge` then they are definately not equal, so return `false`.
jjnguy
@Bobby, have you seen my other answer? Is that more along the lines of what you were looking for?
jjnguy
@Justin, sure I understand, but it's not java code is it?
Bobby S
@bobby, it sure is. Oops.....it should be 1 word - `instanceof` that's my typo, sorry.
jjnguy
Does this deal with the degenerate case that I add two different Edges and then modify one Edge to be "identical" to the other? -- that is, do I end up with only one Edge as a result of my modification, or do both exist in the set?
Mark E
@Mark, I have no idea...Great question.
jjnguy
I think the issue is that this is really only a valid technique when the hashCode is unchanging; that could be enforced by somehow making Edge immutable -- that is, its fields used for computation of the hashCode should be marked `final`. (I'm thinking aloud here because it impacts my solution below)
Mark E
+3  A: 

Use a Set instead of a List to store your edge objects and define the Edge equals and hashCode correctly. This will prevent duplicates which are logically equal.

Syntax
Looks like Justin beat me to it :)
Syntax
@Syntax, yeah...lots of caffeine helps I find!
jjnguy
+1 for being right though.
jjnguy
+1  A: 

To say that you do not want "duplicates", first you have to tell java how you consider two edges are equal. You do this using the equals method (otherwise there is no way for java to know when two of the objects of your Edge class are equal).
When you give a equals method to your object, you have to give a hashCode to your object. See this question for an explanation.
(Actually you oveerride these methods. Default implementations of both are present in java.lang.Object from which all java objects extend)

So first you have to tell java when two Edges are equal. Add a equals method and a hashcode method to your class. See Justin's answer for details.

Once done, you have make sure that your collection of edge objects do not have duplicates. For this you can use a HashSet. (and not a List. A List can have duplicates. A Set cannot. This tutorial gives a good explanation)

In the statement, edges.add..., edges should be a HashSet (or any implementation of set).
HashSet<Edge> edges = new HashSet<Edge>();

Once you have done this, you have a List (er, Set) of unique Edges.

If you need only a List and not a set, you can create a List out of this Set:

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

Nivas
+1  A: 

Another possible way is to just check edges.contains() before adding the new edge.

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

You will need to properly implement the equals method in Edge in order for this to work though. See my other answer for that code.

Note: This way is a much slower way than using a Set to exclude duplicates.

jjnguy
I tried this, all goes well, but I am still with my original error. So now I feel I may have thought the problem is wrong.I added you to twitter, add me back and I can send you my code.
Bobby S
@Bobby. Well, this answered your original question, so if you have another question, you should ask it in a separate question.
jjnguy
+1  A: 

Based on your comments I think really all you need is a customized ArrayList.

On a side note, based on your comments to other answers it seems you need to do a lot of random access by index and not by parameter. That is, you indicated you want to get an Edge by the order in which it was added rather than by specifying unique-ifying parameters, and that you may want to access that order in a highly non-sequential way. For the random access case, this data structure's performance will be better than the custom Set proposed above, because it pays a high upfront-cost of O(N) insert time, where N grows with the size of the set; however, when you go to do random access you get the same great O(1) by index retrieval time that an ArrayList offers. In comparison, for the custom Set, even if you extend a LinkedHashSet to maintain access order you'd pay O(1) insert time, but O(N), to iterate through the entries in order, random access time.

Basically, you pay the unique-ifying constraint upfront, and forget about it later. In the case of the set, you pay an access time constraint later. Of course, that's not the end of the story, there is definitely a solution which might let both worlds be satisfied. Suppose we keep more data (ah, memory vs. CPU/time), and in the data structure below instead of searching through the list to find out if we already have the Edge in question we maintain an internal Set as well. When we go to insert, we first try finding the Edge in the Set (O(1)), when the Edge is unfound, we add it to the Set and to the List. Now, we have to keep an extra Set around (that's the memory cost) to avoid a time penalty for looking at our List during inserts.

So far so good, but we now need to also define a hashing function which generates unique hashCodes for, well, what you consider unique. In other words, we want to make sure, loosely: hashCode(Edge a) != hashCode(Edge b) where a != b should hold for some unbelievably large space of a and b being different in some sense you define. Given you define such a function you still have to pay the memory cost for keeping a set, but that's maybe not so bad if you really care about the insert cost.

Here's the simple straight List concept, it's easily modified to also maintain a Set, and you would modify the contains function below to check the Set rather than check search the List if you wanted to go with the hybrid approach.

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

}

Usage would then be as you suggest, where Edges already contained in the EdgeArrayList will simply not be added:

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

At the end of this foo contains just the first instance of an Edge with all parameters set to 0 (I've tested this, it really works).

Some pitfalls here -- checking that a new Edge is not the list will incur an O(N) hit on the order of the size of the current list N.

Mark E
Good call. That looks like it would get the job done.
jjnguy
I'm getting this error on complile:'{' expected public class EdgeArrayList extends ArrayList<Edge>()
Bobby S
@Bobby: Sorry, try it now.
Mark E
@Mark, SO is not a very good compiler. :P I always have issues.
jjnguy
@Mark Another error - 65: non-static variable this cannot be referenced from a static context EdgeArrayList edges = new EdgeArrayList();
Bobby S
@Bobby: you must have a conflict with your existing code. this works for me: http://ideone.com/2dJjF
Mark E
I need to pass the edge object to another method, I had done so before with shortest(edges); and then public static void shortest(Vector<Edge> edges){}
Bobby S
@Bobby, put this class in its own file or at the bottom of your current file, but not as a subclass of something. The this keyword is referencing the parent class when this is a subclass, and that's not the desired approach.
Mark E
This is the general idea of what I would do. Custom data structures are the way to go. (But I would use a set)
jjnguy
Some kind of hash-backed structure is almost certainly better for performance, as others have mentioned. The performance of this structure will deteriorate severely as the list gets larger.
Matthew Wilson
@Matthew, @Justin, yes, for *inserts* Sets would be much faster in this case, but the fact is the OP wants to *randomly access* the Edges by insert order. There's no built-in style of `Set` that will grant you O(1) random access. Read the OP's comments on @Justin's first answer, it's quite telling. While this solution degrades as O(N) in the *insert*, it's O(1) in the access, and that's, in my view, what really matters here.
Mark E
A: 

This seems like a candidate for a factory where in you could check for an existing object for the criteria inside the factory implementation and return the one already existing.. just my 2 cents...

Siri