Python的ChainMap for Java?

时间:2015-06-19 20:16:32

标签: java python configuration

我有一个深度嵌套的配置麻烦。

问题恰好出现在机器学习中,最终用户调用交叉验证例程,可能会或可能不会指定任何各种参数(例如" randomSeed" = 17)

无论哪种方式,参数必须首先传递给交叉验证算法,然后传递给第一个机器学习算法。机器学习算法必须能够设置并传递其他参数,所有这些都不需要初始用户知道。

参数用户链中的大多数消费者都期望从一个java Map接口进行查找。

由于性能原因(CPU和内存)(' root key-name'空间)将被使用而无需修改数千次,并且每次都将按键展平到一个库中是没有吸引力的在传递包之前,需要指定许多其他参数。

一个不错的模拟是PATH变量如何工作,路径中的每个元素都是一个目录(key-namespace)。当针对PATH变量进行查询时(例如,您在命令行键入' emacs'),它会按顺序查找该文件名(指定值)的每个目录(键的未命名命名空间) ,直到它找到它,或者找不到它。如果找到它,则可以执行它找到的可执行文件的特定内容(获取参数集的值)。如果您有另一个PATH变量,则可以在将PATH变量设置传递给新的最终用户时在其前面附加一个新目录(匿名密钥空间),而不修改以前的目录(首选项)。 / p>

鉴于配置参数上的名称空间实际上是平的,像Python ChainMap这样的解决方案将是完美的(例如example usage),但我找不到相应的解决方案在Java?

3 个答案:

答案 0 :(得分:1)

周末我继续创建ChainMap实施;感谢Java 8,这是一个非常小的课程。我的实施与你的实施略有不同;它没有尝试反映Python的行为,而是遵循Map接口的规范。值得注意的是:

  • 查找顺序是插入顺序;传递给构造函数的第一个映射优先于以下映射。
  • .containsValue()与早期地图屏蔽的值不匹配。
  • .put()会返回链图的先前值,即使该值位于以后的地图中。
  • .remove()从所有地图中删除密钥,而不仅仅是第一个地图或可见条目。来自Javadoc:" 一旦呼叫返回,地图将不包含指定密钥的映射。"
  • 同样.clear()清除所有地图,而不仅仅是顶部地图。
  • 根据其条目集实现.equals().hashCode(),以便它等于其他Map实现。

我也没有实现推/弹行为,因为它感觉像是反模式; ChainMap已经是一系列地图的O(1)视图,您可以根据需要使用所需的地图构建其他ChainMap

显然,如果您的实施适用于您的用例,那就太棒了。但它在几个地方违反了Map合同;我强烈建议删除implements Map<K, V>,然后让它成为一个独立的类。

许多班级的方法都很好,例如:

@Override
public int size() {
  return keySet().size();
}

@Override
public boolean isEmpty() {
  return !chain.stream().filter(map -> !map.isEmpty()).findFirst().isPresent();
}

@Override
public boolean containsKey(Object key) {
  return chain.stream().filter(map -> map.containsKey(key)).findFirst().isPresent();
}

@Override
public boolean containsValue(Object value) {
  return entrySet().stream()
    .filter(e -> value == e.getValue() || (value != null && value.equals(e.getValue())))
    .findFirst().isPresent();
}

@Override
public V get(Object key) {
  return chain.stream().filter(map -> map.containsKey(key))
    .findFirst().map(map -> map.get(key)).orElse(null);
}

我已经写了一些tests来验证班级的行为。欢迎额外的测试用例。

我还扩展了使用Maps.asMap()创建地图集合的不可变视图的想法;如果你不需要突变,这将很好地工作。 (作为I learned,您必须使用.reduce()的三参数形式来使泛型行为表现。)

public static <K, V> Map<K, V> immutableChainView(
    Iterable<? extends Map<? extends K, ? extends V>> maps) {
  return StreamSupport.stream(maps.spliterator(), false).reduce(
    (Map<K,V>)ImmutableMap.<K,V>of(),
    (a, b) -> Maps.asMap(Sets.union(a.keySet(), b.keySet()),
                         k -> a.containsKey(k) ? a.get(k) : b.get(k)),
    (a, b) -> Maps.asMap(Sets.union(a.keySet(), b.keySet()),
                         k -> a.containsKey(k) ? a.get(k) : b.get(k)));
    }

答案 1 :(得分:0)

开箱即用,我不知道等效物。番石榴也不提供Maps.chain()方法,但也许它应该。

对于正常用例,我只想构建一个新的Map;使用番石榴,你可以简洁地这样做,例如:

// Entries in map2 overwrite map1; reverse the insertion order if needed
ImmutableMap.builder().putAll(map1).putAll(map2).build();

您可以通过扩展ChainMap<K, V>来定义自己的List<Map<? extends K, ? extends V>>实施,以便相对轻松地存储AbstractMap。然而,这会引入O(k)开销(其中k是链中的映射数)。如果您的地图太大以至于复制太多,或者您打算经常构建这些链式映射,那么这是值得的,但我不会建议它。

对于合并属性这样的用例,我只想构建一个新的地图并完成它。

答案 2 :(得分:0)

由于似乎没有现成的东西(感谢lopisan和dimo414的提示/指针),我做了第一次破解实施,至少可以满足我的需求。如果有人知道图书馆级版本,我会暂时将此标记作为答案。

我已经包含了很多示例用法调用。它们可以转换为单元测试。有些地方可以提高效率。

import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;


/** 
 * Not thread safe. To make it thread safe, you'd also have to ensure the underlying collections are thread safe.
 * Expected use case is  indexing Strings
 */
public class ChainMap<K, V>  implements Map<K, V>, Cloneable{ 
    ArrayList<  Map<K, V> > scopes = new ArrayList<>(); 

    @Override
    public V get(Object key) {

        for( Map<K, V> m : Lists.reverse( scopes ))
            if( m.containsKey( key)) return  m.get(key);

        //no one has it..
        return null; 
    }


    public  void pushScope( Map< K, V>  scope){
        scopes.add( scope); 
    }

    public  Map<K, V>  popScope( ) {
        if( scopes.size() == 0) throw new RuntimeException("must have at least one underlying map in the Chain to do a pop"); 

        return scopes.remove( scopes.size() -1); 
    }


    /** warning, this risks being expensive, as the semantics are interpreted as the count of distinct keys. 
     * you may want to cache this value*/ 
    public int size() {        return keySet().size();    }


    public boolean isEmpty() {
        for(  Map<K, V> m : scopes  )         //no reverese iteration  needed
            if( !m.isEmpty()) return false; 
        return true; 
    }


    public boolean containsKey(Object key) { 
        for(  Map<K, V> m : scopes  )       //no reverese iteration  needed
            if( m.containsKey( key)) return true;   
        return false; 
    }


    public boolean containsValue(Object value) {
        for(  Entry<K, V> e : entrySet())
            if( (value == e.getValue() || (value != null && value.equals(e.getValue()))) == true) 
                return true; 

        return false; 
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Map) {
            return entrySet().equals(((Map<?,?>)obj).entrySet());
        }
        return false;
    }

    @Override
    public int hashCode() {
        return entrySet().hashCode();
    }


    public V put(K key, V value) {
        if( scopes.size() == 0) throw new RuntimeException("must have at least one underlying map in the Chain to do a put"); 

        return scopes.get( scopes.size()-1).put( key, value); 
    }


    public V remove(Object key) {
        return scopes.get( scopes.size()-1).remove( key); 
    }


    public void putAll(Map<? extends K, ? extends V> m) {
        if( scopes.size() == 0) throw new RuntimeException("must have at least one underlying map in the Chain to do a put"); 
        scopes.get( scopes.size()-1).putAll( m); 
    }


    /** clears only the last, by default */
    public void clear() { 
        scopes.get( scopes.size()-1).clear();   
   }


    /** builds the result as a view on the underlying keySet */
    public Set<K> keySet() {
        int numMaps = scopes.size(); 
        if( numMaps == 0) return Collections.emptySet(); 
        else if( numMaps == 1) return scopes.get(0).keySet(); 
        else{ 

            Set<K> result = Sets.union( scopes.get(numMaps-1).keySet(), scopes.get(numMaps-2).keySet()); 

            for (int i = scopes.size()-3; i >=0 ; i--) 
                result = Sets.union( result, scopes.get( i).keySet());

            return result; 
        }
    }

    public Collection<V> values() {
        return this.entrySet().stream().map( e -> e.getValue()).collect(Collectors.toList()); 
    }

    /** builds the result as a view on the underlying entrySets */
    public Set<Map.Entry<K, V>> entrySet() {
        int numMaps = scopes.size(); 
        if( numMaps == 0) return new HashMap<K, V>().entrySet();
        else if( numMaps == 1) return scopes.get(0).entrySet(); 
        else{ 

            Set<K> keySet = this.keySet(); 
            Map<K, V> m = Maps.asMap( keySet,    key -> this.get(key)); 
            return m.entrySet(); //return Maps.asMap( keySet,    key -> this.get(key)).keySet();  
        }
    }


    @SuppressWarnings("unchecked")
    public Object clone(){
        try {
            ChainMap< K, V> cm  = (ChainMap< K, V>) super.clone();
            cm.scopes = (ArrayList<  Map<K, V> > ) this.scopes.clone();
            return cm; 
        } catch (CloneNotSupportedException e) {
            throw new Error( e ); 
        }
    }


    public ChainMap<K, V> copy(){ 
        @SuppressWarnings("unchecked")
        ChainMap<K, V> c  = (ChainMap<K, V>) clone();
        return c; 
    }








    public static void 
    examples1
    ( )
    {
        ChainMap<String, Object> cm1 = new ChainMap<>();  
        HashMap<String, Object> a = new HashMap<>();
        a.put( "a", "A"); 
        a.put( "b", "B"); 
        a.put( "c", "C"); 
        a.put( "m", "M"); 
        a.put( "a'sMap", "asValue");  //<-- tracer entry

        HashMap<String, Object> b = new HashMap<>();
        b.put( "c", "CCC"); 
        b.put( "b'sMap", "bsValue");   //<-- tracer entry

        HashMap<String, Object> c = new HashMap<>();
        c.put( "a", "AAA"); 
        c.put( "b",  1); 
        c.put( "z", "ZZZ"); 
        c.put( "c'sMap", "csMapValue");   //<-- tracer entry

        cm1.pushScope( a);
        cm1.pushScope( b);
        cm1.pushScope( c);
        PrintStream o = System.out; 

        o.println( cm1.get( "z")); //prints "ZZZ"
        o.println( cm1.get( "b")); //prints 1

        cm1.put( "z", 5);
        o.println( cm1.get( "z")); //prints 5

        ChainMap<String, Object> cm2 = cm1.copy();

        HashMap<String, Object> d = new HashMap<>();
        d.put( "a", 999);
        d.put( "w", "WWWWWWW");
        d.put( "x", "XXXXXXX");
        d.put( "t", "TTTTTTT");
        d.put( "d'sMap", "dsMapValue");  //<-- tracer entry
        cm2.pushScope(d); 

        ChainMap<String, Object> cm3 = cm2.copy();  

        o.println( cm2.get( "a")); //prints "999"
        o.println( cm1.get( "a")); //prints "AAA"

        cm2.popScope(); 
        cm2.popScope();
        o.println( cm2.get("a"));//prints "A"


        o.println( cm3.keySet().size()); 


        o.println( "__________"); 
        //show how can iterate keys-value pairs
        for( Entry<String, Object>  e: cm3.entrySet()) 
            o.println( e.getKey() + ":" + e.getValue()); 

        o.println( "__________"); 

        o.println( cm3.keySet().contains( "w")); //prints true


        o.println( cm3.containsKey( "f")); //prints false 
        o.println( cm3.containsKey( "a")); //prints true
        o.println( cm3.containsKey( "w")); //prints true


        cm3.popScope(); 
        o.println( cm3.containsKey( "w")); //prints false

    }


    public static void 
    examples2
    ( )
    {
        ChainMap<String, Object> cm1 = new ChainMap<>();  
        HashMap<String, Object> a = new HashMap<>();
        a.put( "a", "A"); 
        a.put( "a'sMap", "asValue"); 

        HashMap<String, Object> b = new HashMap<>();
        b.put( "b", "BBB"); 
        b.put( "b'sMap", "bsValue"); 

        HashMap<String, Object> c = new HashMap<>();
        c.put( "c", "CCC"); 
        c.put( "c'sMap", "csMapValue"); 

        HashMap<String, Object> d = new HashMap<>();
        d.put( "d", "DDD"); 
        d.put( "d'sMap", "dsMapValue"); 

        cm1.pushScope( a);
        cm1.pushScope( b);
        cm1.pushScope( c);
        PrintStream o = System.out; 

        // we can make a chainMap part of another 
        ChainMap<String, Object> cmMeta = new ChainMap<>(); 
        cmMeta.pushScope( cm1);
        cmMeta.pushScope( d);


        o.println( "__________"); 
        for( Entry<String, Object>  e: cmMeta.entrySet()) 
            o.println( e.getKey() + ":" + e.getValue()); 
        o.println( "__________"); 

        /*Gives: 
            __________
            d'sMap:dsMapValue
            d:DDD
            c:CCC
            c'sMap:csMapValue
            b:BBB
            b'sMap:bsValue
            a:A
            a'sMap:asValue
            __________
         */

    }

    public static void   main( String[] args ) {   examples1();  examples2();     }

}

由于这会模拟python ChainMap API,请注意aChainMapInstance.remove(someKey)并不意味着密钥不会在那里。只有当scope-stack上的顶部映射包含密钥时,才会执行remove调用。

(更新为模仿dimo414的containsValue()hashcode()equals()的实现。)

顺便提一下,在尝试构建这个时,我注意到有一个内容版本的两个地图与番石榴的隐式融合(如果这就是你需要的)。一个示例用例:两个映射都有数百万个密钥,您的用户可能根本不打算查询它。但是,与上面的ChainMap不同,修改将为您提供UnsupportedOperationException。这个应该通过嵌套来工作,例如:composedMapView(map1,composedMapView(map2,map3))。

 public static <K, V> 
    Map<K, V> composedMapView( Map<K, V> look1st, Map<K, V> look2nd){
        return  Maps.asMap( Sets.union( look1st.keySet(), look2nd.keySet()), 
                    k ->  look1st.containsKey(k) ? look1st.get(k) : look2nd.get(k));  

    }