Guava: Set + Function = Map?

前端 未结 6 1123
眼角桃花
眼角桃花 2021-02-05 01:40

Is there an idiomatic way to take a Set and a Function, and get a Map live view? (i.e. the Map

相关标签:
6条回答
  • 2021-02-05 02:23

    Caution. Sean Patrick Floyd's answer, although very useful, has a flaw. A simple one, but took me a while to debug so don't fall in the same trap: the MapEntry class requires equals and hashcode implementations. Here are mine (simple copy from the javadoc).

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Entry)) {
            return false;
        }
        Entry<?, ?> e2 = (Entry<?, ?>) obj;
        return (getKey() == null ? e2.getKey() == null : getKey().equals(e2.getKey()))
            && (getValue() == null ? e2.getValue() == null : getValue().equals(e2.getValue()));
    }
    
    @Override
    public int hashCode() {
        return (getKey() == null ? 0 : getKey().hashCode()) ^
            (getValue() == null ? 0 : getValue().hashCode());
    }
    

    This reply would be better as a commentary to the relevant answer, but AFAIU I don't have the right to post a comment (or did't find how to!).

    0 讨论(0)
  • 2021-02-05 02:23

    I don't know if this is what you mean by live view.Any way here is my try.

    public class GuavaTst {
    public static void main(String[] args) {
        final Function<String, String> functionToLower = new Function<String, String>() {
            public String apply (String input) {
                return input.toLowerCase();
            }
        };
    
          final Set<String> set=new HashSet<String>();
          set.add("Hello");
          set.add("BYE");
          set.add("gOOd");
          Map<String, String> testMap = newLiveMap(set,functionToLower);
          System.out.println("Map :- "+testMap);
          System.out.println("Set :- "+set);
          set.add("WoRld");
          System.out.println("Map :- "+testMap);
          System.out.println("Set :- "+set);
          testMap.put("OMG","");
          System.out.println("Map :- "+testMap);
          System.out.println("Set :- "+set);
    
     }
    
    
     static <K,V> Map<K,V> newLiveMap(final Set<K> backEnd,final Function<K,V> fun)
     {
        return new HashMap<K,V>(){
    
    
                @Override
                public void clear() {
    
                    backEnd.clear();
                }
                @Override
                public boolean containsKey(Object key) {
    
                    return backEnd.contains(key);
                }
                @Override
                public boolean isEmpty() {
    
                    return backEnd.isEmpty();
                }
                @Override
                public V put(K key, V value) {
    
                    backEnd.add(key);
                    return null; 
                }
                @Override
                public boolean containsValue(Object value) {
    
                    for(K s:backEnd)
                        if(fun.apply(s).equals(value))
                            return true;
                    return false;
                }
                @Override
                public V remove(Object key) {
    
                    backEnd.remove(key);
                    return null;
                }
                @Override
                public int size() {
    
                    return backEnd.size();
                }
    
                @Override
                public V get(Object key) {
    
                    return fun.apply((K)key);
                }
                @Override
                public String toString() {
    
                    StringBuilder b=new StringBuilder();
                    Iterator<K> itr=backEnd.iterator();
    
                    b.append("{");
                    if(itr.hasNext())
                    {
                     K key=itr.next();  
                     b.append(key);
                     b.append(":");
                     b.append(this.get(key));
    
                     while(itr.hasNext())
                     {
                      key=itr.next();
                      b.append(", ");
                      b.append(key);
                      b.append(":");
                      b.append(this.get(key));   
                     }
                    }
    
                    b.append("}");
    
                    return b.toString();
                }
            };              
     } 
    }
    

    The implementation is not complete and the overridden functions are not tested but I hope it convey's the idea.

    UPDATE:

    I made some small change's to seanizer's answer so that the changes made in map will reflect in the set also.

    public class SetBackedMap<K, V> extends AbstractMap<K, V> implements SetFunctionMap<K, V>{
    
        public class MapEntry implements Entry<K, V>{
            private final K key;
            public MapEntry(final K key){
                this.key = key;
            }
            @Override
            public K getKey(){
                return this.key;
            }
            @Override
            public V getValue(){
                V value = SetBackedMap.this.cache.get(this.key);
                if(value == null){
                    value = SetBackedMap.this.funk.apply(this.key);
                    SetBackedMap.this.cache.put(this.key, value);
                }
                return value;
            }
            @Override
            public V setValue(final V value){
                throw new UnsupportedOperationException();
            }
        }
    
    
    
        public class EntrySet extends AbstractSet<Entry<K, V>>{
    
            public class EntryIterator implements Iterator<Entry<K, V>>{
                private final Iterator<K> inner;
                public EntryIterator(){
                    this.inner = EntrySet.this.keys.iterator();
                }
    
                @Override
                public boolean hasNext(){
                    return this.inner.hasNext();
                }
                @Override
                public Map.Entry<K, V> next(){
                    final K key = this.inner.next();
                    return new MapEntry(key);
                }
                @Override
                public void remove(){
                    throw new UnsupportedOperationException();
                }
    
    
            }
    
            private final Set<K> keys;
    
            public EntrySet(final Set<K> keys){
                this.keys = keys;
            }
            @Override
            public boolean add(Entry<K, V> e) {
                return keys.add(e.getKey());
            }
            @Override
            public Iterator<Map.Entry<K, V>> iterator(){
                return new EntryIterator();
            }
    
            @Override
            public int size(){
                return this.keys.size();
            }
            @Override
            public boolean remove(Object o) {
                return keys.remove(o);
            }
    
        }
    
        private final WeakHashMap<K, V> cache;
        private final Set<Entry<K, V>> entries;
        private final Function<K, V> funk;
    
        public SetBackedMap(final Set<K> keys, final Function<K, V> funk){
            this.funk = funk;
            this.cache = new WeakHashMap<K, V>();
            this.entries = new EntrySet(keys);
        }
    
        @Override
        public Set<Map.Entry<K, V>> entrySet(){
            return this.entries;
        }
    
        public boolean putKey(K key){
            return entries.add(new MapEntry(key));
        }
    
        @Override
        public boolean removeKey(K key) {
            cache.remove(key);
            return entries.remove(key);
        }
    
    
    }
    

    Interface SetFunctionMap:

    public interface SetFunctionMap<K,V> extends Map<K, V>{
         public boolean putKey(K key);
         public boolean removeKey(K key);
    }
    

    Test Code:

    public class SetBackedMapTst {
    public static void main(String[] args) {
        Set<Integer> set=new TreeSet<Integer>(Arrays.asList(
                1, 2, 4, 8, 16));
        final SetFunctionMap<Integer, String> map =
            new SetBackedMap<Integer, String>(set,
                new Function<Integer, String>(){
                    @Override
                    public String apply(final Integer from){
                        return Integer.toBinaryString(from.intValue());
                    }
                });
              set.add(222);
              System.out.println("Map: "+map); 
              System.out.println("Set: "+set);
              map.putKey(112);
              System.out.println("Map: "+map); 
              System.out.println("Set: "+set);
              map.removeKey(112);
              System.out.println("Map: "+map); 
              System.out.println("Set: "+set);
    
    }
    }
    

    Output:

    Map: {1=1, 2=10, 4=100, 8=1000, 16=10000, 222=11011110}//change to set reflected in map 
    Set: [1, 2, 4, 8, 16, 222]
    Map: {1=1, 2=10, 4=100, 8=1000, 16=10000, 112=1110000, 222=11011110}
    Set: [1, 2, 4, 8, 16, 112, 222]//change to map reflected in set 
    Map: {1=1, 2=10, 4=100, 8=1000, 16=10000, 222=11011110}
    Set: [1, 2, 4, 8, 16, 222]//change to map reflected in set 
    
    0 讨论(0)
  • 2021-02-05 02:25

    For the non live view the code exists in lambdaJ with Lambda.map(Set, Converter).

    Set<K> setKs = new Set<K>();
    Converter<K, V> converterKv = new Converter<K,V>{
        @Override
        public V convert(K from){
            return null; //Not useful here but you can do whatever you want
        }
    }
    Map<K, V> mapKvs = Lambda.map(setKs, converterKv);
    

    I tried my own implementation : http://ideone.com/Kkpcn As said in the comments, I have to extends another class so I just implemented Map, that's why there is so much code.

    There is a totally useless (or not ?) feature that allows you to change the converter on the fly.

    0 讨论(0)
  • 2021-02-05 02:34

    what about Maps.uniqueIndex()

    0 讨论(0)
  • 2021-02-05 02:44

    Creating a Map from a Set and a Function

    Here are two classes that should each do the job. The first just shows a map view of the set, while the second can write values back to the set through a special interface.

    Call Syntax:

    Map<K,V> immutable = new SetBackedMap<K,V>(Set<K> keys, Function<K,V> func);
    Map<K,V> mutable = new MutableSetBackedMap<K,V>(Set<K> keys, Function<K,V> func);
    

    Where to put this code?

    Side note: If guava were my library, I'd make them accessible through the Maps class:

    Map<K,V> immutable = Maps.immutableComputingMap(Set<K> keys, Function<K,V> func);
    Map<K,V> mutable = Maps.mutableComputingMap(Set<K> keys, Function<K,V> func);
    

    Immutable version:

    I have implemented this as a one-way view:

    • Changes to the set are reflected in the map, but not vice-versa (and you can't change the map anyway, the put(key, value) method isn't implemented).
    • The entrySet() iterator uses the set iterator internally, so it will also inherit the internal iterator's handling of ConcurrentModificationException.
    • Both put(k,v) and entrySet().iterator().remove() will throw UnsupportedOperationException.
    • Values are cached in a WeakHashMap, with no special concurrency handling, i.e. there is no synchronization at any level. This will do for most cases, but if your function is expensive, you might want to add some locking.

    Code:

    public class SetBackedMap<K, V> extends AbstractMap<K, V>{
    
        private class MapEntry implements Entry<K, V>{
            private final K key;
            public MapEntry(final K key){
                this.key = key;
            }
            @Override
            public K getKey(){
                return this.key;
            }
            @Override
            public V getValue(){
                V value = SetBackedMap.this.cache.get(this.key);
                if(value == null){
                    value = SetBackedMap.this.funk.apply(this.key);
                    SetBackedMap.this.cache.put(this.key, value);
                }
                return value;
            }
            @Override
            public V setValue(final V value){
                throw new UnsupportedOperationException();
            }
        }
    
        private class EntrySet extends AbstractSet<Entry<K, V>>{
    
            public class EntryIterator implements Iterator<Entry<K, V>>{
                private final Iterator<K> inner;
                public EntryIterator(){
                    this.inner = EntrySet.this.keys.iterator();
                }
                @Override
                public boolean hasNext(){
                    return this.inner.hasNext();
                }
                @Override
                public Map.Entry<K, V> next(){
                    final K key = this.inner.next();
                    return new MapEntry(key);
                }
                @Override
                public void remove(){
                    throw new UnsupportedOperationException();
                }
            }
    
            private final Set<K> keys;
    
            public EntrySet(final Set<K> keys){
                this.keys = keys;
            }
    
            @Override
            public Iterator<Map.Entry<K, V>> iterator(){
                return new EntryIterator();
            }
    
            @Override
            public int size(){
                return this.keys.size();
            }
    
        }
    
        private final WeakHashMap<K, V> cache;
        private final Set<Entry<K, V>> entries;
        private final Function<? super K, ? extends V> funk;
    
        public SetBackedMap(
            final Set<K> keys, Function<? super K, ? extends V> funk){
            this.funk = funk;
            this.cache = new WeakHashMap<K, V>();
            this.entries = new EntrySet(keys);
        }
    
        @Override
        public Set<Map.Entry<K, V>> entrySet(){
            return this.entries;
        }
    
    }
    

    Test:

    final Map<Integer, String> map =
        new SetBackedMap<Integer, String>(
            new TreeSet<Integer>(Arrays.asList(
                1, 2, 4, 8, 16, 32, 64, 128, 256)),
            new Function<Integer, String>(){
    
                @Override
                public String apply(final Integer from){
                    return Integer.toBinaryString(from.intValue());
                }
            });
    for(final Map.Entry<Integer, String> entry : map.entrySet()){
        System.out.println(
            "Key: " + entry.getKey()
            + ", value: " + entry.getValue());
    }
    

    Output:

    Key: 1, value: 1
    Key: 2, value: 10
    Key: 4, value: 100
    Key: 8, value: 1000
    Key: 16, value: 10000
    Key: 32, value: 100000
    Key: 64, value: 1000000
    Key: 128, value: 10000000
    Key: 256, value: 100000000
    

    Mutable Version:

    While I think it's a good idea to make this one-way, here's a version for Emil that provides a two-way view (it's a variation of Emil's variation of my solution :-)). It requires an extended map interface that I'll call ComputingMap to make clear that this is a map where it doesn't make sense to call put(key, value).

    Map interface:

    public interface ComputingMap<K, V> extends Map<K, V>{
        boolean removeKey(final K key);
        boolean addKey(final K key);
    }
    

    Map implementation:

    public class MutableSetBackedMap<K, V> extends AbstractMap<K, V> implements
        ComputingMap<K, V>{
    
        public class MapEntry implements Entry<K, V>{
    
            private final K key;
    
            public MapEntry(final K key){
                this.key = key;
            }
    
            @Override
            public K getKey(){
                return this.key;
            }
    
            @Override
            public V getValue(){
                V value = MutableSetBackedMap.this.cache.get(this.key);
                if(value == null){
                    value = MutableSetBackedMap.this.funk.apply(this.key);
                    MutableSetBackedMap.this.cache.put(this.key, value);
                }
                return value;
            }
    
            @Override
            public V setValue(final V value){
                throw new UnsupportedOperationException();
            }
    
        }
    
        public class EntrySet extends AbstractSet<Entry<K, V>>{
    
            public class EntryIterator implements Iterator<Entry<K, V>>{
    
                private final Iterator<K> inner;
    
                public EntryIterator(){
                    this.inner = MutableSetBackedMap.this.keys.iterator();
                }
    
                @Override
                public boolean hasNext(){
                    return this.inner.hasNext();
                }
    
                @Override
                public Map.Entry<K, V> next(){
                    final K key = this.inner.next();
                    return new MapEntry(key);
                }
    
                @Override
                public void remove(){
                    throw new UnsupportedOperationException();
                }
    
            }
    
            public EntrySet(){
            }
    
            @Override
            public Iterator<Map.Entry<K, V>> iterator(){
                return new EntryIterator();
            }
    
            @Override
            public int size(){
                return MutableSetBackedMap.this.keys.size();
            }
    
        }
    
        private final WeakHashMap<K, V> cache;
        private final Set<Entry<K, V>> entries;
        private final Function<? super K, ? extends V> funk;
        private final Set<K> keys;
    
        public MutableSetBackedMap(final Set<K> keys,
            final Function<? super K, ? extends V> funk){
            this.keys = keys;
            this.funk = funk;
            this.cache = new WeakHashMap<K, V>();
            this.entries = new EntrySet();
        }
    
        @Override
        public boolean addKey(final K key){
            return this.keys.add(key);
        }
    
        @Override
        public boolean removeKey(final K key){
            return this.keys.remove(key);
        }
    
        @Override
        public Set<Map.Entry<K, V>> entrySet(){
            return this.entries;
        }
    
    }
    

    Test:

    public static void main(final String[] args){
        final ComputingMap<Integer, String> map =
            new MutableSetBackedMap<Integer, String>(
                new TreeSet<Integer>(Arrays.asList(
                    1, 2, 4, 8, 16, 32, 64, 128, 256)),
                new Function<Integer, String>(){
    
                    @Override
                    public String apply(final Integer from){
                        return Integer.toBinaryString(from.intValue());
                    }
                });
        System.out.println(map);
        map.addKey(3);
        map.addKey(217);
        map.removeKey(8);
        System.out.println(map);
    }
    

    Output:

    {1=1, 2=10, 4=100, 8=1000, 16=10000, 32=100000, 64=1000000, 128=10000000, 256=100000000}
    {1=1, 2=10, 3=11, 4=100, 16=10000, 32=100000, 64=1000000, 128=10000000, 217=11011001, 256=100000000}
    
    0 讨论(0)
  • 2021-02-05 02:44

    Guava 14 now has Maps.asMap for a view of the Set and Maps.toMap for an immutable copy.

    You can see much of the discussion of the issues involved here: https://github.com/google/guava/issues/56

    0 讨论(0)
提交回复
热议问题