番石榴:Set<K> + Function<K,V> = Map<K,V>?

Posted

技术标签:

【中文标题】番石榴:Set<K> + Function<K,V> = Map<K,V>?【英文标题】:Guava: Set<K> + Function<K,V> = Map<K,V>? 【发布时间】:2011-04-21 14:53:44 【问题描述】:

有没有一种惯用的方法来获取Set&lt;K&gt;Function&lt;K,V&gt;,并获得Map&lt;K,V&gt; 实时视图? (即MapSetFunction 组合支持,例如,如果将一个元素添加到Set,则相应的条目也存在于Map 中。

(请参阅例如Collections2.filter 以了解有关实时视图的更多讨论)


如果不需要实时取景怎么办?还有比这更好的吗:

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;

【问题讨论】:

“实时取景”是什么意思? 啊,这听起来可能非常有用。 稍微改进一下是使用 Maps.newHashMapWithExpectedSize(keys.size()) 还有Maps.transformEntries(),它是活的,但不是正确的签名。 Guava 现在将其提供为 Maps.asMap... 【参考方案1】:

Guava 14 现在有 Maps.asMap 用于查看 Set,Maps.toMap 用于不可变副本。

您可以在这里看到很多关于所涉及问题的讨论: https://github.com/google/guava/issues/56

【讨论】:

而且已经过去了两年左右,所以不要指望它会很快发生...... 不,不是很快,但看起来它最终会出现在 Guava 14(@Beta)中。【参考方案2】:

我不知道这是否是您所说的实时取景。任何方式都是我的尝试。

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();
            
        ;              
  

实现不完整,覆盖的功能没有测试,但我希望它传达的想法。

更新:

我对@9​​87654321@ answer 做了一些小改动,这样在地图中所做的更改也将反映在集合中。

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);
    



接口SetFunctionMap:

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

测试代码:

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);



输出:

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 

【讨论】:

@seanizer:手动是指函数 put(key,value) 吗?但是这个函数不起作用。它会抛出不受支持的异常。所以映射键的唯一方法是使用该函数。 我误会了你。等几分钟,我正在寻找更好的答案。【参考方案3】:

从集合和函数创建映射

这里有两个类应该各自完成这项工作。第一个只是显示集合的地图视图,而第二个可以通过特殊接口将值写回集合。

调用语法:

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);

把这段代码放在哪里?

旁注:如果 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);

不可变版本:

我已将其实现为单向视图:

对集合的更改反映在 地图,但反之亦然(无论如何您都无法更改地图,put(key, value) 方法未实现)。 entrySet() 迭代器使用 在内部设置迭代器,因此它将 也继承了内部迭代器的 处理 ConcurrentModificationExceptionput(k,v)entrySet().iterator().remove()会 扔 UnsupportedOperationException。 值缓存在WeakHashMap中, 没有特殊的并发处理,即没有同步 任何级别。这适用于大多数情况,但如果您的功能很昂贵,您可能需要添加一些锁定。

代码:

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;
    


测试:

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());

输出:

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

可变版本:

虽然我认为单向是一个好主意,但这里有一个 Emil 版本,它提供了双向视图(它是 Emil 对我的解决方案的变体的一种变体 :-))。它需要一个扩展的地图接口,我将调用ComputingMap 以明确这是一个调用put(key, value) 没有意义的地图。

地图界面:

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

地图实施:

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;
    


测试:

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);

输出:

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

【讨论】:

我只想将final Function&lt;K, V&gt; funk 更改为final Function&lt;? super K, V&gt; funk,但这确实是一个不错的实现:) @Colin 是的,但必须是 final Function&lt;? super K, ? extends V&gt; funk 才能更准确:-)。我用这个版本更新了我的代码。 @seanizer:我觉得如果能覆盖 toString 就好了,这样就可以直接打印地图了。 @Emil 很明显,但是AbstractMap 已经提供了hashCode()equals()toString() @seanizer: 其实我的意思是如果我们可以像这样打印地图 1:1, 2:10 ,4:100... 而不是 [1,2,4 ..] 这是 toString 的当前输出【参考方案4】:

对于非实时视图,代码存在于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);

我尝试了自己的实现:http://ideone.com/Kkpcn 正如 cmets 中所说,我必须扩展另一个类,所以我只是实现了Map,这就是为什么有这么多代码。

有一个完全没用(或没有?)的功能可让您随时更改转换器。

【讨论】:

【参考方案5】:

Maps.uniqueIndex()呢

【讨论】:

那是倒退。 OP 希望计算值,而不是键。【参考方案6】:

小心。肖恩帕特里克弗洛伊德的答案虽然非常有用,但有一个缺陷。一个简单的,但我花了一些时间来调试所以不要落入同一个陷阱:M​​apEntry 类需要equals 和hashcode 实现。这是我的(来自 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());

此回复作为对相关答案的评论会更好,但 AFAIU 我无权发表评论(或没有找到如何发表评论!)。

【讨论】:

+1:确实,按照Map.Entry的合约,这两种方法都是必须的。 (而且你目前缺乏 *** 的“声誉”来发布 cmets。通过写好答案,您可以增加“声誉”并解锁本网站的许多功能。) 我花了 3 年时间才看到这个答案 :-) 当然没错,但作为一般经验法则:在 SO 上看到的任何代码都不应被视为完整的生产就绪解决方案,即使如果它有时包含有价值的想法。

以上是关于番石榴:Set<K> + Function<K,V> = Map<K,V>?的主要内容,如果未能解决你的问题,请参考以下文章

番石榴库:Iterators.cycle() 线程安全吗?

将excel表格导入番石榴表

参数 'Set<Future<DocumentReference<Map<String, dynamic>>>> Function(String)' 不

番石榴的英文名称是啥

排序

排序