Python';Java的链图?
我有一个非常复杂的配置问题 问题恰好出现在机器学习中,在机器学习中,调用交叉验证例程的最终用户可以指定,也可以不指定任何不同的参数(例如,“randomSeed”=17) 无论哪种方式,参数都必须首先传递给交叉验证算法,然后再传递给第一个机器学习算法。机器学习算法必须能够在初始用户不知道的情况下设置和传递其他参数 参数链中的大多数用户都希望java映射接口能够从中进行查找 出于性能方面的原因,将密钥平铺到一个库中是没有吸引力的——CPU和内存都是如此——“根密钥名称”空间将被使用数千次而无需修改,并且每次在传递捆绑包之前都需要指定一些附加参数 一个合理的模拟是PATH变量的工作方式,PATH中的每个元素都是一个目录(键名称空间)。当对PATH变量进行查询(例如,在命令行中键入“emacs”)时,它会按顺序在每个目录(未命名的键名称空间)中查找该文件名(指定值),直到找到它或找不到它为止。如果它找到它,您就可以执行它找到的可执行文件的特定内容(获取参数集的值)。如果您有另一个路径变量,则在将该路径变量设置传递给新的最终用户时,可以在其前面附加一个新目录(匿名密钥空间),而无需修改以前的目录(首选项)Python';Java的链图?,java,python,configuration,Java,Python,Configuration,我有一个非常复杂的配置问题 问题恰好出现在机器学习中,在机器学习中,调用交叉验证例程的最终用户可以指定,也可以不指定任何不同的参数(例如,“randomSeed”=17) 无论哪种方式,参数都必须首先传递给交叉验证算法,然后再传递给第一个机器学习算法。机器学习算法必须能够在初始用户不知道的情况下设置和传递其他参数 参数链中的大多数用户都希望java映射接口能够从中进行查找 出于性能方面的原因,将密钥平铺到一个库中是没有吸引力的——CPU和内存都是如此——“根密钥名称”空间将被使用数千次而无需修改
鉴于配置参数上的名称空间实际上是平坦的,像Python这样的解决方案将是完美的(例如),但我在Java中找不到等效的解决方案 开箱即用,我不知道有哪一种等效的。Guava也没有提供
Maps.chain()
方法,但也许它应该提供
对于正常用例,我只需构建一个新的映射
;使用番石榴,您可以简洁地做到这一点,例如:
// Entries in map2 overwrite map1; reverse the insertion order if needed
ImmutableMap.builder().putAll(map1).putAll(map2).build();
您可以定义自己的
ChainMap
实现来存储列表,因为似乎没有现成的东西(感谢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(); }
}
在周末,我继续并创建了一个实现;多亏了Java8,它是一个非常小的类。我的实现与你的略有不同;它不尝试镜像Python的行为,而是遵循接口的规范。值得注意的是:
- 查找顺序为插入顺序;传递给构造函数的第一个映射优先于以下映射
.containsValue()
与早期映射所掩盖的值不匹配
.put()
返回链映射的上一个值,即使该值在以后的映射中也是如此
.remove()
从所有映射中移除密钥,而不仅仅是第一个映射或可见条目。来自Javadoc:“一旦调用返回,映射将不包含指定键的映射。”
- 类似地,
.clear()
清除所有贴图,而不仅仅是顶部贴图
- 基于其条目集实现
.equals()
和.hashCode()
,以便与其他Map
实现相同
我也没有实现push/pop行为,因为它感觉像是一种反模式ChainMap
已经是一系列地图的O(1)视图,您只需根据需要使用所需的地图构造额外的ChainMap
s即可
显然,如果您的实现适合您的用例,那就太好了。但它在几个地方违反了Map
合同;我强烈建议删除实现Map
,让它成为一个独立的类
该类的许多方法都是很好的一行程序,例如:
@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);
}
我也写了一些来验证类的行为。欢迎添加测试用例
我还扩展了您使用Maps.asMap()
创建地图集合的不可变视图的想法;如果你不需要突变,这将很好地工作。(如图所示,必须使用.reduce()
的三参数形式才能使泛型正常工作)
公共静态映射不可变链视图(
IterableMaybe这可能会有帮助:因为我对Python一无所知,所以解释一下您正在尝试做的事情会有所帮助。@lopisan-这可能是一个解决办法,尽管出于性能原因、使用清晰性和某种程度的调试原因,这不太理想。所寻求的概念与面向对象类型层次结构中的名称解析非常相似,除了这里的“字段”既稀疏又动态。最终用户在尝试访问字段时忘记了名称链是如何创建的。很好!您对containsValue()的改进揭示了我最初实现中的一个错误,以及您的equals()和hashcode()这显然是正确的方法!我感谢您对这三种方法所做的更新。很高兴这很有帮助:)我仍然建议从您的实现中删除implements Map
,因为它不符合Map
的约定。关于put()、remove()、putAll()和clear()的工作原理,似乎是一个最终应用程序使用驱动的主题。我在寻找Java的ChainMap时遇到了这个问题,但总而言之,我认为关注Java Map接口的设计意图是更好的方法。我会核对一下答案。;在这个实现中,要获得只修改顶部映射的python链映射行为,您可以
@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);
}
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)));
}