简体   繁体   English

番石榴:设置<K> +功能<K,V> =地图<K,V>?

[英]Guava: Set<K> + Function<K,V> = Map<K,V>?

Is there an idiomatic way to take a Set<K> and a Function<K,V> , and get a Map<K,V> live view? 是否有惯用的方法来获取Set<K>Function<K,V> ,并获得Map<K,V>实时视图? (ie the Map is backed by the Set and Function combo, and if eg an element is added to the Set , then the corresponding entry also exists in the Map ). (即, MapSetFunction组合支持,如果例如将一个元素添加到Set ,则相应的条目也存在于Map )。

(see eg Collections2.filter for more discussion on live views) (有关实时视图的更多讨论,请参阅例如Collections2.filter


What if a live view is not needed? 如果不需要实时视图怎么办? Is there something better than this: 有没有比这更好的东西:

public static <K,V> Map<K,V> newMapFrom(Set<K> keys, Function<? super K,V> f) {
    Map<K,V> map = Maps.newHashMap();
    for (K k : keys) {
        map.put(k, f.apply(k));
    }
    return map;
}

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: 旁注:如果guava是我的库,我可以通过Maps类访问它们:

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). 对集合的更改将反映在地图中,但反之亦然(并且无论如何都无法更改地图,未实现put(key, value)方法)。
  • The entrySet() iterator uses the set iterator internally, so it will also inherit the internal iterator's handling of ConcurrentModificationException . entrySet()迭代器在内部使用set迭代器,因此它还将继承内部迭代器对ConcurrentModificationException的处理。
  • Both put(k,v) and entrySet().iterator().remove() will throw UnsupportedOperationException . put(k,v)entrySet().iterator().remove()都将抛出UnsupportedOperationException
  • Values are cached in a WeakHashMap , with no special concurrency handling, ie there is no synchronization at any level. 值被缓存在WeakHashMap ,没有特殊的并发处理,即在任何级别都没有同步。 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 :-)). 虽然我觉得单向制作是一个好主意,但这里是Emil的一个版本,它提供了一个双向视图(它是Emil对我的解决方案变体的变体:-))。 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) . 它需要一个扩展的地图界面,我将其称为ComputingMap ,以明确这是一个调用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}

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. 一个简单的,但我花了一段时间来调试所以不要陷入同一个陷阱:M​​apEntry类需要equals和hashcode实现。 Here are mine (simple copy from the javadoc). 这是我的(来自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!). 作为对相关答案的评论,这个回复会更好,但AFAIU我无权发表评论(或者没有找到如何!)。

Guava 14 now has Maps.asMap for a view of the Set and Maps.toMap for an immutable copy. Guava 14现在具有Maps.asMap用于查看不可变副本的Set和Maps.toMap

You can see much of the discussion of the issues involved here: https://github.com/google/guava/issues/56 您可以在此处查看有关问题的大部分讨论: https//github.com/google/guava/issues/56

For the non live view the code exists in lambdaJ with Lambda.map(Set, Converter) . 对于非实时视图,代码存在于LambdaJ中,带有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. 我尝试了自己的实现: http//ideone.com/Kkpcn如评论中所述,我必须扩展另一个类,所以我只是实现了Map ,这就是为什么有这么多代码的原因。

There is a totally useless (or not ?) feature that allows you to change the converter on the fly. 有一个完全无用的(或没有?)功能,允许您动态更改转换器。

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. 我对seanizer的 答案做了一些小改动,以便地图中所做的更改也会反映在集合中。

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: 接口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 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM