ac1692 / google-collections

Automatically exported from code.google.com/p/google-collections
Apache License 2.0
0 stars 0 forks source link

Suggestion: Maps.fromFunction #56

Closed GoogleCodeExporter closed 9 years ago

GoogleCodeExporter commented 9 years ago
This method would create a new hash map, then iterate over keys: apply
function to get value, add new entry to the map.

Map<K, V> fromFunction(Set<K> keys, Function<K, V> function);

Original issue reported on code.google.com by sebd...@gmail.com on 2 Apr 2008 at 11:03

GoogleCodeExporter commented 9 years ago
This comes up occasionally, but has never been very compelling.  Can you provide
motivation?  What works is:

(a) A scenario that feels pretty common
(b) Some ugly code to handle that scenario today
(c) What the code would look like if Maps.fromFunction() existed.

Original comment by kevin...@gmail.com on 27 May 2008 at 7:02

GoogleCodeExporter commented 9 years ago
I keep writing the following method in many projects:
    public static <K, V> Map<K, V> hashMapFromFunction(Set<K> keys, Function<K, V>
function) {
          Map<K, V> map = Maps.newHashMapWithExpectedSize(keys.size());
          for (K key : keys) {
            map.put(key, function.apply(key));
          }
          return map;
    }

A common scenario would be to cache the result of applying the function to each
element, for example to later sort using the (cached values of the) function.

Nothing really ugly here, but one could for example forget to set the initial 
size of
the map.

Original comment by sebd...@gmail.com on 25 Aug 2008 at 2:26

GoogleCodeExporter commented 9 years ago

I just came up with the same/similar idea and found this item. This is how I 
would
imagine this feature.

Function<String, String> functionToLower = new Function<String, String>() {
    Sring apply (String input) {
        return input.toLower();
    }
}

Map<String, Object> testMap = 
Maps.createHashMapWithKeyFunction(functionToLower);

testMap.put("AKEY", someObject);

asssertTrue(testMap.contains("akey"));

assertEquals(someObject, testMap.get("akey");

The function basically enables you to map your keys to a different Object which 
in
turn means you can use custom equals and hashcode implementations just for that 
map.
Is there any other way to do this? 

Maybe this could also be implemented as part of the map maker.

Original comment by Florian....@gmail.com on 2 Apr 2009 at 9:10

GoogleCodeExporter commented 9 years ago
This would be extremely counter-intuitive for a map where you both write and 
read. 
(But for a write-only scenario it seems ok).

testMap.put("AKEY", someObject);
assertTrue(testMap.containsKey("AKEY")); // ??

In any case, can't you use a ForwardingMap to transform the keys before you 
apply 
them to the delegate map

---
Another similar proposal (I don't think it's too compelling though):
Given a bijection A<-->B (i.e. both Function<A, B> and Function <B, A>), create 
from 
a Map<A, V> a map view of type Map<B, V>, and have Maps#transformKeys along 
with 
Map#transformValues.

(The Function<A, B> is only needed for implementing Map#keySet of the view if I 
recall correctly, if it weren't for that, a Function<B, A> would probably 
suffice).

Original comment by jim.andreou on 2 Apr 2009 at 2:41

GoogleCodeExporter commented 9 years ago
My proposal was meant as a custom hash function which is applied while storing 
values
AND getting values from the Map. In the example a value stored with a lower 
case key
could also be retrieved with an upper case key. 

I'll look into extending a ForwardingMap to provide this functionality and I 
realized
it's a bit different from what was requested here initially.

Meanwhile, I found that Maps.uniqueIndex() and MultiMaps.index() work well 
enough for
my usecases. 

Original comment by Florian....@gmail.com on 9 Apr 2009 at 6:23

GoogleCodeExporter commented 9 years ago
i just wrote this trivial implementation, since i have a lot of code like this 
in my
Enums:

    static final Map<String, CountryEnum> reverseLookup;

    static {
        HashMap<String, CountryEnum> ret = new HashMap<String, CountryEnum>();
        for (CountryEnum oneCountry : CountryEnum.values()) {
            ret.put(oneCountry.right, oneCountry);
        }
        reverseLookup = ret;
    }

public static <K,V> ImmutableMap<K,V> mapValues(V[] data , Function<V,K> 
keyGetter){
        return mapValues(Arrays.asList(data),keyGetter);
    }
    public static <K,V> ImmutableMap<K,V> mapValues(Iterable<V> data , Function<V,K>
keyGetter){
        ImmutableMap.Builder<K,V> b = ImmutableMap.builder();
        for (V value : data) {
            b.put(keyGetter.apply(value),value);
        }
        return b.build();
    }

this reduces the code to :

    static final Map<String, CountryEnum> reverseLookup = CommonUtils.mapValues(
        CountryEnum.values(),new Function<CountryEnum, String>() {      
        public String apply(CountryEnum from) {
            return from.right;
        }
    });

this is used a lot when the same enum is represented in the database by 
different
types of keys (sometimes ISO country code, sometimes "internal" code sometimes 
id..)

this flavor produces a ImmutableMap, which is what i need in most cases.
but there could be a lot of other possibilities:
lazy mapping - only evaluate the given function when it is needed.
you will not need to evaluate it when just iterating the values.
you will not need to evaluate all of them it when you say 
mappedMap.get(someValue)
and the first element of the iterator would already return "someValue".
in case of such a lazy initialization, one would need to maintain a partially 
filled
hashmap. the actual lookup of values would be split in two parts: 1) lookup in
intermediate map if not found 2) iterate until key is found. while iterating 
fill
intermediate map.
3) if iterator is at its end, the map may become a immutableMap?

semantics get complicated if you try to insert in such a 
iterable+function-backed
map. even more so, if it is yet only partially evaluated.

Original comment by heroldsi...@googlemail.com on 13 May 2009 at 12:21

GoogleCodeExporter commented 9 years ago
Wow... V[]? Shouldn't that beast be abolished? Prefer List<V>.

Original comment by jim.andreou on 13 May 2009 at 12:30

GoogleCodeExporter commented 9 years ago
V[] is an overloading, to use .mapValues(..) together with Enums, which provide 
the
.values() method (which provide only an array). List<V> would already be 
covered by
Iterable<V>

concise is nice.

Original comment by heroldsi...@googlemail.com on 13 May 2009 at 12:49

GoogleCodeExporter commented 9 years ago
I agree concise is nice, but small api surface is also nice; if this was 
applied 
consistently across google collections, it would easily end up doubling many of 
its 
methods... (Sorry, I commented without grokking the full code, just skimmed 
through 
it).

Original comment by jim.andreou on 13 May 2009 at 1:19

GoogleCodeExporter commented 9 years ago
[deleted comment]
GoogleCodeExporter commented 9 years ago
i agree, not having arrays is consistent with the google-maps api

instead of CountryEnum.values() which produces an array i could as well use
EnumSet.allOf(CountryEnum.class) which is Iterable. - so V[] is not really 
needed.

on second thought the keyGetter argument does not strictly need to be typed 
with V, ?
super V is sufficient.

on a related note, i have overlooked Maps.uniqueIndex this does already exactly 
what
i need. i feel bad :(

Original comment by heroldsi...@googlemail.com on 13 May 2009 at 3:35

GoogleCodeExporter commented 9 years ago

Original comment by kevin...@gmail.com on 17 Sep 2009 at 5:57

GoogleCodeExporter commented 9 years ago

Original comment by kevin...@gmail.com on 17 Sep 2009 at 6:02

GoogleCodeExporter commented 9 years ago
I often find myself creating a maps where the key is based on the value, rather 
than
the other way around. So the form

Map<K, V> fromFunction(Set<V> values, Function<V, K> function);

would also be appreciated.

Original comment by davemi...@gmail.com on 21 Oct 2009 at 9:41

GoogleCodeExporter commented 9 years ago
@daveminor

What would that fromFunction method do if the provided Function was not 
one-to-one?  
(Do you want a Multimap instead?)

Original comment by cresw...@gmail.com on 21 Oct 2009 at 9:51

GoogleCodeExporter commented 9 years ago
We already have that functionality in Maps.uniqueIndex(), or Multimaps.index() 
for
the case creswick mentioned.

Original comment by jared.l....@gmail.com on 21 Oct 2009 at 9:57

GoogleCodeExporter commented 9 years ago
Ah, thank you - overlooked it because of the name.

Original comment by gmtetest@gmail.com on 21 Oct 2009 at 11:59

GoogleCodeExporter commented 9 years ago
This issue has been moved to the Guava project (keeping the same id number). 
Simply replace 'google-collections' with 'guava-libraries' in your address 
bar and it should take you there.

Original comment by kevinb@google.com on 5 Jan 2010 at 11:09