4

Is there something in Guava that allows me to get the inverse of a Multimap as a (non-multi-) Map?

Consider the following:

static final ImmutableMap<Token, Integer> precedences =
      new ImmutableSetMultimap.Builder<Integer, Token>()
      .put(0, NOT)
      .put(1, ASSIGN)
      .put(2, OR)
      .put(3, AND)
      .putAll(4, NOT_EQUAL, EQUALS)
      .putAll(5, LESS_EQUAL, LESS, GREATER_EQUAL, GREATER)
      .putAll(6, ADD, SUB)
      .putAll(7, MUL, DIV, MOD).build().inverse();

The problem is that the inverse() is a Multimap again, and not a Map. Is there something in Guava that does the conversion for me or do I have to roll my own utility function?

S1lentSt0rm
  • 1,989
  • 2
  • 17
  • 28
  • 1
    No, there's nothing for this that does this conversion, because it's not always valid: it's entirely possible for multiple keys to map to the same value, which would cause a failure when you tried to do the inversion. Why not build the `Map` _first_ and then invert that into a `Multimap`, which is much easier? – Louis Wasserman Nov 04 '16 at 22:53
  • I'm aware of the fact that the conversion would fail, if the values are not unique across all entries (hence the title). I don't need the `Multimap` at all, it's just nicer to write and read it that way. – S1lentSt0rm Nov 04 '16 at 22:58

1 Answers1

6

The best I can think of is to use map view of a multimap and Maps.transformValues view (I'm assuming Java 8 here, otherwise use Function instead of method reference):

static final ImmutableMap<Token, Integer> PRECEDENCES = ImmutableMap.copyOf(
        Maps.transformValues(TOKENS.inverse().asMap(), Iterables::getOnlyElement));

Using Java 8 streams the above would be:

static final Map<Token, Integer> PRECEDENCES =
        TOKENS.inverse().asMap().entrySet().stream()
                .collect(Collectors.collectingAndThen(
                        Collectors.toMap(
                                Map.Entry::getKey,
                                e -> Iterables.getOnlyElement(e.getValue())),
                        ImmutableMap::copyOf));

or if you care about the order:

static final ImmutableMap<Token, Integer> PRECEDENCES =
        TOKENS.inverse().asMap().entrySet().stream()
                .collect(Collectors.collectingAndThen(
                        Collectors.toMap(
                                Map.Entry::getKey,
                                e -> Iterables.getOnlyElement(e.getValue()),
                                (u, v) -> {
                                    throw new IllegalStateException(String.format("Duplicate key %s", u));
                                },
                                LinkedHashMap::new),
                        ImmutableMap::copyOf));

or hopefully in Guava 21:

static final Map<Token, Integer> PRECEDENCES =
        TOKENS.inverse().asMap().entrySet().stream()
                .collect(ImmutableMap.toImmutableMap(
                                Map.Entry::getKey,
                                e -> Iterables.getOnlyElement(e.getValue())));
Grzegorz Rożniecki
  • 27,415
  • 11
  • 90
  • 112
  • 1
    I was hoping for a single method call that I have missed :( So basically I have to roll my own utility method... but thanks for the pointers, implementing this is at least pretty easy with the utilities guava provides – S1lentSt0rm Nov 04 '16 at 22:31