【问题标题】:Collections with compound keys and hierarchical defaults具有复合键和分层默认值的集合
【发布时间】:2014-10-28 20:23:35
【问题描述】:

我想根据键存储值。

键可以是复合键,每个键最多包含两个组件。

我想要地图

Compound key {A,null} to Value 1

此值 1 将是所有以 A 作为其第一个组件的键的查找的默认值,除非已将完全匹配的键添加到映射中。

所以如果我添加到地图中

Compound key {A,Z} having Value 2

当我进行查找时,我希望 {A,*} 类型的所有查找都返回 1,所以 {A,F} 返回 1,因为它没有指定,所以回退到默认值。 {A,Z} 是个例外,它会返回明确指定的 2。

我可以从第一原则做到这一点,通过在检查一个组件匹配之前检查是否存在精确的键(两个组件)匹配。

但是,是否有现有的收藏可以为我做这件事?

如果我有任意数量的组件怎么办?

【问题讨论】:

    标签: java collections hashmap


    【解决方案1】:

    “如果我有任意数量的组件怎么办?”

    那么,不要再有一些警告了,然后用隐藏得很好的原始地图去老学校。

    import java.util.HashMap;
    import java.util.Map;
    
    
    /**
     *  Map lookup with arbitrary number of keys, as set with first use of lay()
     *  Missing keys map to null if null key exists
     */
    public class MultiKeyMap<K, V>
    {
        int expectedNumberOfKeys = -1;
        V value;
    
        @SuppressWarnings("rawtypes")
        Map<K, Map> topMap = new HashMap<K, Map>();
    
        /** Map to value from keys */
        @SuppressWarnings({ "rawtypes", "unchecked" })
        public V lay(V value, K... keys)
        {
            if (keys == null)
            {
                //there are no keys.
                expectedNumberOfKeys = 0;
                V oldValue = this.value;
                this.value = value; 
                return oldValue;
            }
    
            if (expectedNumberOfKeys != -1 && expectedNumberOfKeys != keys.length)
            {
                throw new IllegalArgumentException("Expecting " + expectedNumberOfKeys + " keys.  Was " + keys.length );
            }
    
            expectedNumberOfKeys = keys.length;
    
            Map<K, Map> currentMap = topMap; 
    
            //all but last key
            for(int i = 0; i < keys.length - 1; i++)
            {
                K key = keys[i];
    
                currentMap = linkToNextMap(currentMap, key);
            }
    
            //last key
            V oldValue = ((Map<K,V>)currentMap).put(keys[keys.length - 1], value); 
            return oldValue;
    
        }
    
        @SuppressWarnings({ "rawtypes", "unchecked" })
        Map<K,Map> linkToNextMap(Map<K,Map> map, K key)
        {
            Map<K, Map> nextMap = null;
    
            if ( ! map.containsKey(key) )
            {
                map.put(key, new HashMap<K, Map>() );
            }
    
            nextMap = map.get(key);
    
            return nextMap;
        }
    
        /** 
         * Get value maped from keys.  Must include as many keys as laid down.  
         * Keys not found are taken as null keys 
         */
        @SuppressWarnings({ "rawtypes", "unchecked" })
        public V get(K... keys)
        {
            if (keys == null)
            {
                return value;
            }
    
            //System.out.println(topMap+" <- topMap");//TODO remove
    
            if (expectedNumberOfKeys == -1)
            {
                return null;
            }
    
            if (expectedNumberOfKeys == 0)
            {
                return value;
            }
    
            if (expectedNumberOfKeys != keys.length)
            {
                throw new IllegalArgumentException("Expecting " + expectedNumberOfKeys + " keys.  Was " + keys.length );
            }
    
            Map<K, Map> currentMap = topMap;
    
            //All but last key
            for(int i = 0; i < keys.length - 1; i++)
            {
                currentMap = (Map) getDefault(currentMap, keys[i]);
            }
    
            //Last key
            V result = (V) getDefault(currentMap, keys[keys.length - 1]);
    
            return result;
        }
    
        @SuppressWarnings("rawtypes")
        Object getDefault(Map map, K key)
        {
            Object result = null;
    
            if (map != null)
            {
    
                //Use default key (null) if not found
                if ( ! map.containsKey(key) )
                {
                    key = null;
                }
    
                result = map.get(key);
            }
    
            return result;
        }
    
        public static void main(String[] args)
        {
            //Build {null={D=4, null=3}, A={null=1, Z=2}} 
            MultiKeyMap<String, Integer> map2 = new MultiKeyMap<String, Integer>();
            map2.lay(1, "A", null);
            map2.lay(2, "A", "Z");
            map2.lay(3, null, null);
            map2.lay(4, null, "D");
            System.out.println(map2.get("A", null)); //1        
            System.out.println(map2.get("A", "Z"));  //2
            System.out.println(map2.get("A", "F"));  //1 F not found so treating as null
            System.out.println(map2.get(null, null));//3 
            System.out.println(map2.get(null, "D")); //4
            System.out.println(map2.get("F", "D"));  //4 F not found so treating as null
            System.out.println();
    
            //Build {null={D={C=4}, null={C=3}}, A={null={B=1}, Z={B=2}}} 
            MultiKeyMap<String, Integer> map3 = new MultiKeyMap<String, Integer>();
            map3.lay(1, "A", null, "B");
            map3.lay(2, "A", "Z", "B");
            map3.lay(3, null, null, "C");
            map3.lay(4, null, "D", "C");
            System.out.println(map3.get("A", null, "B")); //1   
            System.out.println(map3.get("A", "Z", "B"));  //2
            System.out.println(map3.get("A", "F", "B"));  //1 F not found so treating as null
            System.out.println(map3.get(null, null, "C"));//3
            System.out.println(map3.get(null, "D", "C")); //4
            System.out.println(map3.get("F", "D", "C"));  //4 F not found so treating as null
        }   
    }
    

    显示:

    1
    2
    1
    3
    4
    4
    
    1
    2
    1
    3
    4
    4
    

    我知道没有人会为此投票,但我无法入睡,直到我把它从脑海中浮出水面。

    晚安。

    【讨论】:

      【解决方案2】:

      对于这样的事情,我使用Map&lt;String, Map&lt;String, Integer&gt;&gt; 你可以随意嵌套。

      事实证明这适用于空值:

          Map<String, Map<String, Integer>> mapOfMap = new HashMap<String, Map<String, Integer>>();
      
          //Make one of these for every first key
          Map<String, Integer> mapOfInt = new HashMap<String, Integer>(); 
      
          mapOfInt.put(null, 1); 
          mapOfInt.put("Z", 2); 
      
          mapOfMap.put("A", mapOfInt);
      
      
          System.out.println(mapOfMap.get("A").get(null));
          System.out.println(mapOfMap.get("A").get("Z"));
      

      显示:

      1
      2
      

      如果你想让一个班级隐藏所有细节(我不会怪你)试试这个:

      import java.util.HashMap;
      import java.util.Map;
      
      public class DoubleKeyMap<K1, K2, V>
      {
          Map<K1, Map<K2, V>> mapOfMap;
      
          public void put(K1 key1, K2 key2, V value)
          {
              if (mapOfMap == null)
              {
                  mapOfMap = new HashMap<K1, Map<K2, V>>();
              }
      
              if ( ! mapOfMap.containsKey(key1) )
              {
                  mapOfMap.put(key1, new HashMap<K2, V>() );
              }
      
              mapOfMap.get(key1).put(key2, value);
      
          }
      
          public V get(K1 key1, K2 key2)
          {
              if ( ! mapOfMap.containsKey(key1) )
              {
                  key1 = null;
              }
              if ( ! mapOfMap.get(key1).containsKey(key2) )
              {
                  key2 = null;
              }
              return mapOfMap.get(key1).get(key2);
          }
      
          public static void main(String[] args)
          {
              DoubleKeyMap<String, String, Integer> bigMap = new DoubleKeyMap<String, String,Integer>();
              bigMap.put("A", null, 1);
              bigMap.put("A", "Z", 2);
      
              System.out.println( bigMap.get("A", null) );
              System.out.println( bigMap.get("A", "Z") );
              System.out.println( bigMap.get("A", "F") );
          }
      }
      

      显示:

      1
      2
      1
      

      【讨论】:

      • 感谢您的参与!我还需要 bigMap.get("A", "F") 返回 1,回退到 {A, null} 默认值。
      • @user1717259 你的意思是这样吗? :)
      • 糟糕!是的,就这样!
      • @user1717259 如果您有需要的答案,请考虑接受答案。
      • 谢谢 - 让我试着了解一下您的其他解决方案!
      【解决方案3】:

      我认为简单的Map 无法满足以下要求:

      When I do a look up, I would like ALL look ups of type {A,*} to return 1, with the exception of {A,Z}, which returns 2.

      确实,想象一下这是可能的,并且map.get(new Compound(A,B)) 返回与new Compound(A,null) 关联的值。那么,这意味着new Compound(A,B)new Compound(A,null) 将具有相同的哈希码并且相等。现在,如果我执行map.put(new Compound(A,B),3),它将覆盖与new Compound(A,null) 关联的值,这不是您想要的。

      您需要实现自己的类型来执行此操作。该实现可以为具有非空第二个组件的复合值包装Map&lt;Compound&lt;T1,T2&gt;,Integer&gt;,为其他组件包装Map&lt;T1,Integer&gt;。您将首先查看第一张地图,如果找不到匹配项,请尝试使用第二张地图。我看不出有任何方法可以以智能的方式将其扩展到任意数量的组件。

      【讨论】:

        【解决方案4】:

        尚未彻底考虑对其他方法的任何潜在影响,但您可以做的是覆盖Hashmap.get()

        public V get(Object key) { V ret = super.get(key); if (ret==null){ //safe cast to Compound key Compound c=...; ret = super.get(new Compound(c.firstPart, null)); } return ret; } 您可以创建一个允许更多成分的复合键,您还可以定义它这样做的顺序。 例如 Compound.getGeneralizedKey() 以您的搜索顺序返回一个新的 Compound 无效成分。

        话虽如此,请注意您的每个get 操作现在都是 O(n) 操作。即对于每个get,您将需要执行get n 次。

        PS。如果您不需要实现Map 接口,最好覆盖Hashmap 添加更安全的方法,例如getRecursive(Compound key)

        【讨论】:

          猜你喜欢
          • 2023-03-28
          • 1970-01-01
          • 1970-01-01
          • 2018-11-06
          • 1970-01-01
          • 1970-01-01
          • 2022-01-20
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多