9

I have an ArrayList which contains element of a class Event. Event has two properties, Name and Timestamp. The list now shows ALL events. I want to remove the duplicates with the same name, but different timestamp, and put them in another list. This way the user can click on an event with that name, then select a date.

I am already overriding the function equals (that compares name AND timestamp) for some other functionalities in my application.

How can I solve this?

Dheeresh
  • 143
  • 2
  • 8
Nicolas Zawada
  • 497
  • 1
  • 3
  • 12
  • have you tried using HashSet instead of ArrayList? – Abubakkar Apr 30 '15 at 10:22
  • do you have any situation like same name and same time stamp and you want to save that also in list ? – Keval Apr 30 '15 at 10:22
  • which Events you must keep? first found? – Jordi Castilla Apr 30 '15 at 10:22
  • Please follow this link [here](http://stackoverflow.com/questions/562894/java-detect-duplicates-in-arraylist). This shows the alternative for a list. If possible you can go by this way. Comment otherwise – XylemRaj Apr 30 '15 at 10:27
  • 1
    Why was this marked as a duplicate? The duplicate link is not answering the question, only a small part of it, and it's doing it wrong based on the other part. I quote: *"I want to remove the duplicates with the same name, but different timestamp, **and put them in another list**."*. It seems that the filtering isn't even the actual goal here: *"This way the user can click on an event **with that name**, then select a date."* – Reut Sharabani Apr 30 '15 at 12:14

3 Answers3

31

If you already have your own equals method you can't use Hash collections. You must manually check it implementing a nested loop:

List<Event> allEvents = // fill with your events.
List<Event> noRepeat = new ArrayList<Event>();

for (Event event : allEvents) {
    boolean isFound = false;
    // check if the event name exists in noRepeat
    for (Event e : noRepeat) {
        if (e.getName().equals(event.getName()) || (e.equals(event))) {
            isFound = true;        
            break;
        }
    }
    if (!isFound) noRepeat.add(event);
}
Dmitriy Fialkovskiy
  • 3,065
  • 8
  • 32
  • 47
Jordi Castilla
  • 26,609
  • 8
  • 70
  • 109
  • I dont think that this is complete solution. _I want remove the duplicates with the same name, but different timestamp, and put them in another list_ You have to check also timmestamps for equality – Ziker Apr 30 '15 at 10:40
  • he has an equals method to check both parameters, he needs to delete same named events from a list: OP says: *I want remove the duplicates with the same name, but different timestamp* thats what my method do... – Jordi Castilla Apr 30 '15 at 10:48
  • 1
    Yeah but since `ArrayList` can contain duplicates condition should be `if (e.getName().equals(event.getName() || e.equals(event))` to remove also different objects with same name and timestamp – Ziker Apr 30 '15 at 10:49
  • actually it goes against natural logic, but it's true... nice catch! – Jordi Castilla Apr 30 '15 at 10:55
  • @JordiCastilla a nice way to do what you're doing is use a custom `Comparator` (to compare) with a `TreeSet` (to create a set using that Comparator). It also enforces clarity since the idea is conveyed by using the right interfaces. See last part of my answer. – Reut Sharabani Apr 30 '15 at 11:43
2

I think you're using the wrong data structure. You want to use an implementation of Map and map String (name) to Set<Event> (unique events).

Here is how we test it:

  1. Create some events.
  2. Create Map<String, Set<Event>. this will allow us to map a name to unique events.
  3. Fill the mapping.

So first, we create a collection of events to test:

    Collection<Event> events = new ArrayList<Event>() {
        /**
         * 
         */
        private static final long serialVersionUID = 1L;

        {
            add(new Event("FirstCategory", new Timestamp(0)));
            add(new Event("FirstCategory", new Timestamp(0)));
            add(new Event("FirstCategory", new Timestamp(1)));
            add(new Event("SecondCategory", new Timestamp(2)));
        }
    };

Now we create a mapping between a name and all of it's corresponding unique events:

    Map<String, Set<Event>> eventsByName = new HashMap<String, Set<Event>>();

Now we fill the mapping with unique events for each name:

    for (Event e : events) {
        if (!eventsByName.containsKey(e.getName())) {
            // create new set by name
            eventsByName.put(e.getName(), new HashSet<Event>());

        }
        // add event to existing Set.
        // duplicates will be dropped since it's a `Set`
        eventsByName.get(e.getName()).add(e);

    }

Check what we got:

    System.out.println(eventsByName);

Output:

{
    SecondCategory=[
        Event [name=SecondCategory, timestamp=1970-01-01 02:00:00.002]
    ],
    FirstCategory=[
        Event [name=FirstCategory, timestamp=1970-01-01 02:00:00.0],
        Event [name=FirstCategory, timestamp=1970-01-01 02:00:00.001]
    ]
}

Tip 1:

To get the list of names you only need to look at the Map's keys, which are effectively a Set as well:

System.out.println(eventsByName.keySet());

Output:

[SecondCategory, FirstCategory]

Tip 2:

If this isn't what you expect, and you want a different definition of uniqueness, you can implement a Comparator<Event> and use that with a TreeSet<Event> instead of using the HashSet<Event> which can not accept a custom Comparator.

So if you have a class:

class EventByRandomDefinitionComparator implements Comparator<Event>{
    // implementation ...
}

This is all that needs to be done when filling the mapping:

    // create different comparison mechanism
    Comparator<Event> comparator = new EventByRandomDefinitionComparator();

    for (Event e : events) {
        if (!eventsByName.containsKey(e.getName())) {
            // create new set by name
            // changed Set implementation to use new comparator
            eventsByName.put(e.getName(), new TreeSet<Event>(comparator)));
        }
        // add event to existing Set.
        // duplicates will be dropped since it's a `Set`
        eventsByName.get(e.getName()).add(e);

    }

Good luck.

Reut Sharabani
  • 30,449
  • 6
  • 70
  • 88
  • Thank you for your comprehensive answer. It would have been a lot easier if I used a map from the beginning. Another answer solved my question, but I will keep Map in my mind for the future. I will use a Map now to link the same event with multiple timestamps. – Nicolas Zawada Apr 30 '15 at 12:06
  • Whatever is stopping you from using the right solution now, is only going to get worse if you add more code that is based on the wrong implementation. Using the right tools can make code easier to maintain, test and understand. If this answer is lacking something you're trying to accomplish add it to your question and I can try and extend the answer to include it. – Reut Sharabani Apr 30 '15 at 12:13
-1

You should override your equals() and hashCode() methods in your Event class and add all the objects in a Set rather than a List. A Set will not allow the duplicate objects, provided you have properly overridden the equals() and hashCode().

Aakash
  • 2,029
  • 14
  • 22