Olivier Grégoire
Olivier Grégoire

Reputation: 35447

Having a Multimap sorted on keys only in Java

I would like to have a c.g.c.c.Multimap that is sorted based on keys only. The values shouldn't be sorted. I've tried to build something with guava's TreeMultimap, but I can't use it because the value type doesn't implement Comparable.

public class MyObject /* doesn't implement Comparable */ {
  private String name;
  private int score;
  // Getters/setters are implemented
  public static Function<MyObject,Integer> myObjectToScore {
    @Override public Integer apply (MyObject o) { return o.score; }
  }
  public static Multimap<Integer,MyObject> indexOnScore(Iterable<MyObject> i) {
    Multimap<Integer,MyObject> m = Multimaps.index(i, myObjectToScore());
    // Do the sort of the keys.
    return m;
  }
}

I've thought about getting a SortedSet of the keys, then iterating over each of these keys in the sorted set to fetch the various values, but I was hoping using an existing (yet undiscovered) feature in Guava rather than using this kind of hack.

Note: I won't make MyObject implement Comparable because it makes no sense with my actual object.


Example of input/output:

Set<MyObject> s = Sets.newHashSet(
  new MyObject("a", 2),
  new MyObject("b", 3),
  new MyObject("c", 1),
  new MyObject("d", 3),
  new MyObject("e", 1)
); // Assuming constructor MyObject(String name, int score)

for (Map.Entry<Integer, MyObject> e: MyObject.indexedOnScore(s).entries()) {
  System.out.printf("%d -> %s%n", e.getKey(), e.getValue().getName());
}

Prints:

1 -> c // or switched with line below
1 -> e
2 -> a
3 -> b // or switched with line below
3 -> d

Upvotes: 26

Views: 25093

Answers (8)

gdejohn
gdejohn

Reputation: 7579

MultimapBuilder was introduced in Guava 16:

<K extends Comparable<? super K>, V> ListMultimap<K, V> multimap() {
    return MultimapBuilder.treeKeys().linkedListValues().build();
}

That keeps your keys sorted by their natural order (MultimapBuilder::treeKeys is also overloaded to accept a custom comparator), and the values associated with each key are maintained in a LinkedList (ArrayList and HashSet are among the other options).

Upvotes: 18

Rahul k
Rahul k

Reputation: 69

Best solution which always works for me is to use Multimap & TreeMultiMap. this will order results in ascending order on keys even if you have multiple duplicate keys. Solution below:

Multimap<Double, Integer> map= TreeMultimap.create(Ordering.natural().reverse(),         Ordering.natural());

if (!map.isEmpty()) {               
                printMap(map);
            }

public static <K, V> void printMap(Multimap<Double, Integer> map) throws Exception {
        for (Map.Entry<Double, Integer> entry : map.entries()) {
            System.out.println("Key : " + entry.getKey() 
                + " Value : " + entry.getValue());              
        }
    }

Upvotes: 0

justkt
justkt

Reputation: 14766

You can do it with TreeMultimap if you use Comparators.

Create a Comparator for the key type and the value type (MyObject?). Then use create(Comparator keyComparator, Comparator valueComparator) to make the map.

The benefit of using a Comparator over implementing Comparable is that you can make the Comparator specific to the situation that you want with the map and it doesn't effect your object in general. As long as your Comparator is consistent with equals it can do whatever you want.

Upvotes: 1

GFonte
GFonte

Reputation: 451

I'd like to point out that the alternative proposed solution, namely "to create a TreeMultimap and use Ordering.arbitrary() as the Comparator for the values", only works if MyObject doesn't override equals() or hashcode(). Ordering.arbitrary() is inconsistent with equals and uses object identity instead, which makes it not a good idea to use it in conjunction with a TreeSet.

Upvotes: 2

Trevor Robinson
Trevor Robinson

Reputation: 16564

Though the OP's specific situation seems to have been answered using immutable multimap building functions, I needed a mutable version of what he was asking for. In case it helps anyone, here's the generic method I ended up creating:

static <K, V> Multimap<K, V> newTreeArrayListMultimap(
    final int expectedValuesPerKey)
{
    return Multimaps.newMultimap(new TreeMap<K, Collection<V>>(),
        new Supplier<Collection<V>>()
        {
            @Override
            public Collection<V> get()
            {
                return new ArrayList<V>(expectedValuesPerKey);
            }
        });
}

Upvotes: 8

Jared Levy
Jared Levy

Reputation: 2026

Call Multimaps.newMultimap, which gives you the flexibility to create, for example, a Multimap backed by TreeMap whose values are ArrayLists.

Upvotes: 4

ColinD
ColinD

Reputation: 110054

Multimaps.index returns an ImmutableListMultimap, so you wouldn't be able to sort it after creating it. You could, however, first create a sorted copy of your Iterable<MyObject> and feed that to Multimap.index... ImmutableListMultimap keeps things in the same order it was given them.

public static ImmutableMultimap<Integer, MyObject> indexOnScore(Iterable<MyObject> i) {
  List<MyObject> sorted = Ordering.natural().onResultOf(myObjectToScore())
      .sortedCopy(i);
  return Multimaps.index(sorted, myObjectToScore());
}

Another option might be to create a TreeMultimap and use Ordering.arbitrary() as the Comparator for the values.

Upvotes: 22

Paul Blessing
Paul Blessing

Reputation: 3845

How about this:

    public static Multimap<Integer, MyObject> indexOnScore(Iterable<MyObject> i) {
        Multimap<Integer, MyObject> m = Multimaps.index(i, myObjectToScore());

        Multimap<Integer, MyObject> sortedKeys = Multimaps.newMultimap(
                Maps.<Integer, Collection<MyObject>>newTreeMap(),
                new Supplier<Collection<MyObject>>() {
                    @Override
                    public Collection<MyObject> get() {
                        return Lists.newArrayList(); // Or a Set if appropriate
                    }
                }
        );

        sortedKeys.putAll(m);

        return sortedKeys;
    }

There would be the overhead of creating two separate Multimaps in this case, though.

Upvotes: 1

Related Questions