4

I have a flattened multi-map (a list) of objects which I would like to convert to a list where a 'key' attribute / field (e.g. name below) is unique amongst all entries.

In the case where multiple entries have the same key (name) the entry with the largest creationDate field should be picked.

Example:

List<Object> myList =
[
  {name="abc", age=23, creationDate = 1234L},
  {name="abc", age=12, creationDate = 2345L},
  {name="ddd", age=99, creationDate = 9999L}
]

Should be converted to:

List<Object> = 
[
  {name="abc", age=12, creationDate = 2345L},
  {name="ddd", age=99, creationDate = 9999L}
]

Is there an elegant way (possibly using Guava libraries?) to solve this in Java? I realize I can just try and use a HashMap with name as the key to find all unique entries, but I get the feeling there is a better way to solve this.

Thanks!

Derek Gourlay
  • 279
  • 2
  • 13

3 Answers3

3

If you have the possibility to work with Java 8 I would recommend Streams as the other answers allready told. If not you can go with something like this.

First you sort the List desending by the creationDate. Then you create a TreeSet witch treats all Persons with same name as equal. Therefore only the first (highest) creationDate will be added and further ones ignored.

List<Person> persons = new ArrayList<>();
persons.add(new Person("abc", 23, 1234L));
persons.add(new Person("abc", 12, 2345L));
persons.add(new Person("def", 99, 9999L));

Collections.sort(persons, new Comparator<Person>() {
    public int compare(Person o1, Person o2) {
            return (int) (o2.creationDate - o1.creationDate);
    }
});

Set<Person> personsHashed = new TreeSet<>(new Comparator<Person>() {
    public int compare(Person o1, Person o2) {
        return o2.name.compareTo(o1.name);
    }
});
personsHashed.addAll(persons);
DanielG
  • 118
  • 5
  • Awesome! Thanks! I don't have Java 8 available but this solution is pretty terse / readable. I might throw in a comment about the side-effect of TreeSet only picking the first values (I didn't know about). – Derek Gourlay Jun 18 '14 at 18:27
2

Whether this is considered elegant is rather subjective, but here goes:

static List<Person> byName(Collection<Person> persons) {
    Map<String, Person> personsByName = persons.stream().collect(
        Collectors.groupingBy(Person::getName,
            Collectors.collectingAndThen(
                Collectors.maxBy(
                    Comparator.comparingLong(Person::getCreationDate)),
                p -> p.get())));
    return new ArrayList<Person>(personsByName.values());
}

You can make it slightly more readable by using static imports for the methods of Collectors and Comparator, such as:

import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.maxBy;
import static java.util.Comparator.comparingLong;

Then the code can be slightly shortened to:

static List<Person> byName(Collection<Person> persons) {
    Map<String, Person> personsByName =
        persons.stream().collect(
            groupingBy(Person::getName, collectingAndThen(
                maxBy(comparingLong(Person::getCreationDate)),
                p -> p.get())));
    return new ArrayList<Person>(personsByName.values());
}
VGR
  • 40,506
  • 4
  • 48
  • 63
1

In Java 8 you can use the new Streaming API to take the collection Stream it through filters and then produce your result. That should be able to do what you are looking for.

Alternatively the idea you already considered of dropping them into a HashMap seems pretty straightforward and will give the behaviour you need. I wouldn't try and over-think this :)

Tim B
  • 40,716
  • 16
  • 83
  • 128