Skip to content

Add MultiValuedMap.inverted()#665

Merged
garydgregory merged 1 commit intoapache:masterfrom
paulk-asert:multimapInverse
Dec 19, 2025
Merged

Add MultiValuedMap.inverted()#665
garydgregory merged 1 commit intoapache:masterfrom
paulk-asert:multimapInverse

Conversation

@paulk-asert
Copy link
Contributor

This PR adds an inverse method into the following multimap implementations:

ArrayListValuedHashMap
ArrayListValuedLinkedHashMap
HashSetValuedHashMap
HashSetValuedLinkedHashMap

There is also an inverseInto method added to MultiMapUtils to handle the case of a heterogenous collector.

As well as a test case to the relevant source files. Decorated multimaps throw UnsupportedOperationException by default.

@garydgregory
Copy link
Member

Hi @paulk-asert

Thank you for the PR.

You'll want to run 'mvn' by itself and fix errors before you push. That will run the default goal which includes all our checks.

@paulk-asert
Copy link
Contributor Author

Hi @paulk-asert
Thank you for the PR.
You'll want to run 'mvn' by itself and fix errors before you push. That will run the default goal which includes all our checks.

@garydgregory Should be fixed. Sorry for the earlier noise.

Copy link
Member

@garydgregory garydgregory left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @paulk-asert
Thank you for the PR :)
I have a couple of comments.

*
* @return the inverse MultiValuedMap
*/
default MultiValuedMap<V, K> inverse() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Java 21 has java.util.List.reversed(), so how about inverted() to match the same naming pattern?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed

Comment on lines +124 to +126
final ArrayListValuedHashMap<V, K> result = new ArrayListValuedHashMap<>();
MultiMapUtils.inverseInto(this, result);
return result;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to be so verbose here and for all implementations IMO:

Suggested change
final ArrayListValuedHashMap<V, K> result = new ArrayListValuedHashMap<>();
MultiMapUtils.inverseInto(this, result);
return result;
return MultiMapUtils.inverseInto(this, new ArrayListValuedHashMap<>());

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed

@garydgregory garydgregory changed the title provide an inverse method for multimaps Add MultiValuedMap.inverted() Dec 14, 2025
* @param <K> the dest key type
* @param <V> the dest value type
* @param <M> the dest multimap
* @return the destination
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not make sense to me. What is the destination?

I think the Javadoc needs to make clear that the method creates a new map with the inverted keys

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The utility method doesn't create a new multimap but stores inverted mappings in the supplied collector (destination) which is also returned. The inverted method implementations supply a new multimap as the collector, but it doesn't need to be the same type or be new (to allow aggregation) when using the utility method. I could try to use "collector" everywhere and avoid "destination" if you think that would be clearer?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think collector would be clearer here.
For the return comment, it could say 'the updated collector'

Also it would help to document the valid types for source and collector.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you clarify what you are after for source and collector? The types/generics information isn't sufficient?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

Collection<V> get(K key);

/**
* Returns the inverse MultiValuedMap with inverted mappings.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'the' => 'a new'

'the' implies that there is only one such map

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In mathematical jargon, the thing being returned is the "inverse multimap". I see what you are saying that those not knowing the math jargon might confuse that wording with meaning just one. Then again, totally forgoing the math jargon might be less clear for those that are aware of the term. I'll try to think of a middle ground.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment refers to 'the MultiValueMap' which is a class, not a mathematical concept.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is the name of the abstraction used to represent a multimap, but I understand where you are coming from and I'll see what improvement to the wording I can think of.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

* @return the updated collector
*/
public static <K, V, M extends MultiValuedMap<K, V>>
M invertInto(MultiValuedMap<? extends V, ? extends K> source, M collector) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @paulk-asert
A few items here:

  • I'm wondering if a simpler name like invert would be better since we don't usually see Into in method names. We know from the param names what the source and target are. Using "Into" doesn't add value for me.
  • The parameter name collector might be confusing because a Collector in Java is something else but kind of related. Since we have source, having dest, destination or target would be better IMO.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe invertMerge would better describe what the method does?

Also rather than source and dest, would input and output be clearer?

I don't like the abbreviation 'dest'; it's best to spell names in full as not all users will have English as a first language.

Copy link
Contributor Author

@paulk-asert paulk-asert Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Guava has similar methods to what is proposed here. It uses invertFrom as the utility method and inverse as the instance method. I think invert and invertMerge have some benefits. I'll ponder a change and convert collector to target or similar. Hmmm.. CollectionUtils#collect uses inputCollection and outputCollection. Maybe that convention is the way to go.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed

/**
* A utility method to invert the mappings from a source MultiValuedMap
* to a collector MultiValuedMap. If you are trying to create an
* inverse multimap of the same kind as the original, consider using
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'same kind' is ambiguous.

AIUI, this method returns a MultiValuedMap; is that not the 'same kind' of map as the original?

It's not clear when one should consider using #inverted()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, the intention is that when calling inverted you have the following input -> output class relationships:

ArrayListValuedHashMap -> ArrayListValuedHashMap
ArrayListValuedLinkedHashMap -> ArrayListValuedLinkedHashMap
LinkedHashSetValuedLinkedHashMap -> LinkedHashSetValuedLinkedHashMap
LinkedHashSetValuedHashMap -> LinkedHashSetValuedHashMap

I'll try to think of better wording.

* @param collector add value-to-key mappings here
* @param <K> the collector key type
* @param <V> the collector value type
* @param <M> the collector multimap
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the valid types for M?
Are all multimap types suitable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the wording. Let me know if it makes sense or still sounds confusing to you?

* @param outputMultimap add value-to-key mappings here
* @param <K> the outputMultimap key type
* @param <V> the outputMultimap value type
* @param <M> the outputMultimap multimap with key and value types reversed compare to inputMultimap
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

compare to => compared with

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed


/**
* A utility method to invert the mappings from an inputMultimap
* and add them to an outputMultimap. Some {@code MultiValuedMap}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inputMultimap => input MultiValuedMap
outputMultimap => output MultiValuedMap

inputMultimap and outputMultimap are not Java types, and AFAICT they must both be MultiValuedMap, rather than just Multimap

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed

* and add them to an outputMultimap. Some {@code MultiValuedMap}
* implementations support the {@link MultiValuedMap#inverted()} method.
* These typically return a new multimap instance with the same class as the original.
* Consider using this method instead if you wish to supply a particular
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'this method' is ambiguous here: does it refer to the method inverted() which has just been described, or the method in this class?

I think it would be clearer to describe the usage for the current class method before explaining that there are alternatives.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed

Copy link
Member

@garydgregory garydgregory left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

@garydgregory garydgregory merged commit d7a8201 into apache:master Dec 19, 2025
9 checks passed
garydgregory added a commit that referenced this pull request Dec 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants