26

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
Olivier Grégoire
  • 33,839
  • 23
  • 96
  • 137

8 Answers8

22

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.

ColinD
  • 108,630
  • 30
  • 201
  • 202
  • Yes, that's what I was looking for. And you gave me not one but two possibilities. Thanks a lot! Edit: I however dislike the two sorts in a row, but I'll try to cope with it. – Olivier Grégoire Mar 31 '11 at 15:21
  • 1
    @ogregoire: Well, it doesn't actually sort twice... it just sorts once and then creates the `Multimap` in the same order. It does copy the data from the `Iterable` in to an intermediate `List` though. – ColinD Mar 31 '11 at 17:16
  • 15
    You could also use ImmutableMultimap.builder().orderKeysBy(); or you could use Multimaps.newMultimap() which lets you choose the backing map and collections (use a TreeMap and, say, ArrayList). – Kevin Bourrillion Mar 31 '11 at 22:46
  • 3
    Nice... hadn't noticed `ImmutableMultimap.builder().orderKeysBy()` before. – ColinD Mar 31 '11 at 23:20
  • @ColinD I think it would be very useful to include Kevin's comment in your answer. – assylias Sep 07 '13 at 19:02
  • @ColinD Why not `Ordering.allEqual()` instead of `Ordering.arbitrary()` for the values? The documentation says: "Returns an ordering which treats all values as equal, indicating "no ordering." – Dag Apr 11 '14 at 14:37
  • @Dag: The main reason not to do that would be that `TreeMultimap` uses a `TreeSet` for its values, and if all the values are considered _equal_, it will only end up containing only _one_ of those values per key. Also, `Ordering.allEqual()` was actually added in Guava 13, over a year after I answered this question. =P – ColinD Apr 11 '14 at 15:11
17

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).

gdejohn
  • 7,451
  • 1
  • 33
  • 49
8

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);
            }
        });
}
Trevor Robinson
  • 15,694
  • 5
  • 73
  • 72
4

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

Jared Levy
  • 1,986
  • 13
  • 12
2

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.

GFonte
  • 451
  • 6
  • 10
1

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.

disco crazy
  • 31,313
  • 12
  • 80
  • 83
justkt
  • 14,610
  • 8
  • 42
  • 62
  • 1
    I don't really like this. I said it makes no sense to have `MyObject` implement `Comparable`. An extension of this statement is that I logically don't have a "default" `Comparator` for `MyObject` as well. My goal is really to have a Multimap that has its keys sorted, but the values for one keys shouldn't be sorted. – Olivier Grégoire Mar 31 '11 at 14:56
1

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.

Paul Blessing
  • 3,815
  • 2
  • 24
  • 25
0

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());              
        }
    }
Rahul k
  • 69
  • 3