15

We're currently using Guava for its immutable collections but I was surprised to find that their maps don't have methods to easily create new maps with minor modifications. And on top of that, their builder doesn't allow assigning new values to keys, or removing keys.

So if I wanted to modify just one value, here's what I would like to be able to do:

ImmutableMap<Guid, ImmutableMap<String, Integer>> originalMap = /* get the map */;
ImmutableMap<Guid, ImmutableMap<String, Integer>> modifiedMap =
    originalMap.cloneAndPut(key, value);

Here's what it looks like Guava are expecting me to do:

ImmutableMap<Guid, ImmutableMap<String, Integer>> originalMap = /* get the map */;
Map<Guid, ImmutableMap<String, Integer>> mutableCopy = new LinkedHashMap<>(originalMap);
mutableCopy.put(key, value);
originalMap = ImmutableMap.copyOf(mutableCopy);
/* put the map back */

By doing this I get a new copy of the map with the modification I want. The original copy is untouched and I will be using an atomic reference to put the thing back so the whole setup is thread-safe.

It's just slow.

There is a lot of wasted copying going on under the covers here. Suppose there's 1,024 buckets in the map. That's 1,023 buckets which you're unnecessarily creating all over again (twice each, too), when you could have used those immutable buckets as-is and cloned only one of them.

So I guess:

  1. Is there a Guava utility method buried somewhere for this sort of thing? (It isn't in Maps or on the ImmutableMap.Builder.)

  2. Is there any other Java library which gets this sort of thing right? I am under the impression that Clojure has this sort of thing under the hood but we're not ready to switch languages just yet...

Hakanai
  • 12,010
  • 10
  • 62
  • 132
  • Please file a feature request if you like, but you probably really are looking for a library of proper functional data structures. – Kevin Bourrillion Feb 01 '12 at 06:33
  • 1
    Yep. This is a thing which can be done -- that's what functional languages do -- but that's not what Guava is there for. Guava's e.g. `ImmutableMap` is based on hashing, and that's not going to support efficient update, not without a significant price in query speed. – Louis Wasserman Feb 01 '12 at 06:44
  • Rephrase: Guava's immutable collections are built for fast queries/iteration and minimal memory consumption. That means hashing and arrays, which isn't going to let you support efficient nondestructive updates. – Louis Wasserman Feb 01 '12 at 06:51
  • @Louis Guava could still support the API `originalMap.cloneAndPut(key, value)` or `originalMap.asBuilder().put(key, value)`. – Thomas Jung Feb 01 '12 at 07:43
  • That's got linear overhead, and it's super risky to make users think it's faster than it actually is...your code doesn't really make me realize that it's a slow workaround. – Louis Wasserman Feb 01 '12 at 16:52
  • @Louis Who thinks like that also thinks that new HashMap(map) or clone are constant operations. I'd say not very many Java developers. (Would be nice to settle such somewhat pointless discussions with surveys and to see if Java devs think like you or I think they think.) – Thomas Jung Feb 01 '12 at 18:52
  • I'll grant you that `cloneAndPut` is self-evidently linear, but some developers (like the OP!) might assume `asBuilder().put()` used some smarter tricks to be logarithmic or something. – Louis Wasserman Feb 01 '12 at 18:57
  • Who cares if cloneAndPut() is fast/efficient. Lots of ppl will use these data structures in contexts where their performance isn't a concern (like me). – Kevin Wong Sep 12 '12 at 19:48
  • Please file a feature request if you like, but you probably really are looking for a library of proper functional data structures. – Kevin Bourrillion Sep 26 '12 at 23:21
  • @KevinBourrillion It would be nice if the state of a builder could be copied/cloned. This would allow creating a builder "template" and spawn off copies that allow adding other parameters. – typeracer Nov 11 '19 at 23:57

2 Answers2

6

A bit unexpected the map of Functional Java is mutable like Guava's. The list is immutable though as I would expect.

Googling for "persistent collection java" brought up: pcollections. There are's a Map implementation.

Before actually using any other implementation I would benchmark the memory and performance characteristics against Guava. I would not be surprised if it's still better.

kuporific
  • 10,053
  • 3
  • 42
  • 46
Thomas Jung
  • 32,428
  • 9
  • 84
  • 114
0

One may go with the following, to just duplicate the map once instead of twice:

ImmutableMap<String, Object> originalMap = /* get the map */;
Map<String, Object> modifiedMap = ImmutableMap.<String, Object>builder().putAll( originalMap )
   .put( "new key", new Object() )
   .build();

However, removing a value looks quite less beautiful with an iteration over the existing map, e.g. like this:

ImmutableMap<String, Object> originalMap = /* get the map */;
if( !originalMap.containsKey( "key to remove" ) ) {
   return;
}
ImmutableMap.Builder<String, Object> mapBuilder = ImmutableMap.builder();
for( Map.Entry<String, Object> originalEntry : originalMap.entrySet() ) {
   if( originalEntry.getKey().equals( "key to remove" ) ) {
      continue;
   }
   mapBuilder.put( originalEntry );
}
Map<String, Object> modifiedMap = mapBuilder.build();
MichaelCkr
  • 540
  • 1
  • 7
  • 14
  • I'm pretty sure that would duplicate the map twice - once at the `putAll` call, and once at the `build()` - but it does depend on how the builder is implemented. It would be possible to implement the builder such that it doesn't actually add the entries from the map until you build it, but I don't think that's how it's currently implemented. If you want to reduce it to once, there is a way - stream the map entries, append to the stream, collect the stream back to a map. – Hakanai Mar 09 '22 at 23:53