All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.aksw.commons.collections.cluster.IndirectEquiMap Maven / Gradle / Ivy

There is a newer version: 0.9.9
Show newest version
package org.aksw.commons.collections.cluster;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;

import org.aksw.commons.collections.generator.GeneratorLending;
import org.aksw.commons.collections.generator.GeneratorLendingImpl;

import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;

/**
 * A map-like key-value data structure where keys can be stated as equivalent, and thus become aliases
 * of each other.
 * 
 * Keys can be stated as equivalent using stateEqual (raises exception on conflict) or tryStateEqual (returns the conflict).
 * Clusters can be obtained using getEquivalences() and their associated values can be set using setValue(id, value).
 * 
 * Note, that there is no distinction between null and undef values. In fact, a null value is interpreted
 * as the absence of a value, and such a cluster token will be removed from the tokenToValue map.
 * 
 * @author raven
 *
 * @param 
 * @param 
 */
public class IndirectEquiMap {
	protected Map keyToToken = new HashMap();
	protected Multimap tokenToKeys = LinkedHashMultimap.create(); 
	protected Map tokenToValue = new HashMap();
	
	// Read only view
	protected Multimap tokenToKeysView = Multimaps.unmodifiableMultimap(tokenToKeys);
	

	//protected int nextToken = 0;
	protected GeneratorLending gen = GeneratorLendingImpl.createInt();
	
	/**
	 * Export the state as a map of the sets of equivalent keys and their associated values.
	 * Mostly intended for unit testing.
	 * 
	 * @return
	 */
	public Map, V> dump() {
		Map, V> result = tokenToKeys.asMap().entrySet().stream()
			.collect(Collectors.toMap(
					e -> (Set)e.getValue(),
					e -> tokenToValue.get(e.getKey())));
		
		return result;
	}
	
	public Set keySet() {
		return Collections.unmodifiableSet(keyToToken.keySet());
	}
	
	public Multimap getEquivalences()
	{
		return tokenToKeysView;
	}
	
//	public Map getValueMap() {
//		return tokenToValue;
//	}
	
	public V getValue(Integer token) {
		return tokenToValue.get(token);
	}
	
	/**
	 * Method to set a cluster's value.
	 * Checks whether the validity of the token, i.e. whether a cluster with that id exists.
	 * 
	 * @param token
	 * @param value
	 * @return
	 */
	public V setValue(Integer token, V value) {
		boolean isValidToken = tokenToKeys.containsKey(token);
		if(!isValidToken) {
			throw new RuntimeException("There is no cluster of keys with id " + token);
		}
		
		putWithoutNull(tokenToValue, token, value);
		return value;
	}
	
	
	public Collection getEquivalences(K key) {
		Integer token = keyToToken.get(key);
		Collection result = tokenToKeys.get(token);
		return result;
	}
	
	
	protected void putKeyToken(K key, int token) {
		keyToToken.put(key, token);
		tokenToKeys.put(token, key);
	}
	
	
	/**
	 * Try to make keys equal. The response determines whether there ary any conflicts.
	 * 
	 * @param a
	 * @param b
	 * @return null on success, otherwise an entry of the key's conflicting values.
	 */
	public Entry tryStateEqual(K a, K b) {
		Entry result = tryStateEqual(a, b, null, false);
		return result;
	}

	/**
	 * State keys to be equal - raises an exception on conflicting values.
	 * 
	 * 
	 * @param a
	 * @param b
	 */
	public void stateEqual(K a, K b) {
		Entry conflict = tryStateEqual(a, b);
		if(conflict != null) {
			throw new RuntimeException("Cannot make " + a + " and " + b + " equal due to conflicting values: " + conflict);
		}
	}


	/**
	 * State keys to be equal and sets their values.
	 * Any conflicts are resolved by setting the cluster values to the provided value. 
	 * 
	 * @param a
	 * @param b
	 * @param value
	 */
	public void stateEqual(K a, K b, V value) {
		tryStateEqual(a, b, value, true);		
	}

	/*
	public Collection<> stateEqual(Collection keys) {
		
	}*/
	
	/**
	 * State keys to be equal and sets their values.
	 * Any conflicts are resolved by setting the cluster values to the provided value. 
	 * 
	 * @param keys
	 * @param value
	 */
	public void stateEqual(Collection keys, V value) {
		int newToken = gen.next(); //++nextToken;
		
		for(K key : keys) {
			Integer oldToken = keyToToken.get(key);
			if(oldToken != null) {
				gen.giveBack(oldToken);
				
				tokenToValue.remove(oldToken);
				tokenToKeys.putAll(newToken, tokenToKeys.get(oldToken));
				tokenToKeys.removeAll(oldToken);
			}
			
			putKeyToken(key, newToken);
		}

		tokenToValue.put(newToken, value);
	}
	
	
	/**
	 * States an equality between keys.
	 * 
	 * if overwrite is true, conflicts can not occur as they are overwritten with value. Return value is always null.
	 * if overwrite is false, in case of conflict the pair of conflicting values is returned
	 * 
	 * Conflicts can be resolved using stateEqual(a, b, value)
	 * 
	 * 
	 * @param a
	 * @param b
	 */
	protected Entry tryStateEqual(K a, K b, V value, boolean overwrite) {
		Integer ta = keyToToken.get(a);
		Integer tb = keyToToken.get(b);
		
		if(ta == null) {
			if(tb == null) {
				int token = gen.next(); // ++nextToken;
				
				putKeyToken(a, token);
				putKeyToken(b, token);
				
			} else {
				putKeyToken(a, tb);
			}
		} else {
			if(tb == null) {
				putKeyToken(b, ta);
			} else {
				
				V va = tokenToValue.get(ta);
				V vb = tokenToValue.get(tb);

				if(va != null && vb != null && !va.equals(vb)) {
					// Conflict: Equality stated, but two distinct values
					if(overwrite) {
						va = value;
					} else  {
						return Maps.immutableEntry(va, vb);
					}
				}
				
				if(va == null) {
					va = vb;
				}
				
				// Copy to avoid ConcurrentModificationException
				Collection ka = new ArrayList(tokenToKeys.get(ta));
				Collection kb = new ArrayList(tokenToKeys.get(tb));

				// // Merge the smaller cluster into the larger one
				// Disabled, because it turned out to be more useful to have the merge work deterministically
				// based on the given argument order.
				boolean mergeSmallerClusterIntoLangerOne = false;
				if(mergeSmallerClusterIntoLangerOne) {
					Collection tmp;
					int tt;
					if(kb.size() > ka.size()) {
						tmp = ka;
						ka = kb;
						kb = tmp;
						
						tt = ta;
						ta = tb;
						tb = tt;
					}
				}

				tokenToKeys.removeAll(tb);
				tokenToValue.remove(tb);
				gen.giveBack(tb);
				for(K k : kb) {
					//tokenToKeys.remove(tb, k);
					putKeyToken(k, ta);
				}
//				tokenToValue.remove(tb);


				putWithoutNull(tokenToValue, ta, va);
//				if(va != null) {
//					tokenToValue.put(ta, va);
//				}
			}
		}

		return null;
	}
	
	
	public static  void putWithoutNull(Map map, K key, V value) {
		if(value == null) {
			map.remove(key);
		} else {
			map.put(key, value);
		}
	}
	
	/**
	 * Adds a key.
	 * If it already exists, its associated value is left untouched.
	 * Note, that this behavior is different from put(key, null) - which overwrites the value
	 * with the absence of a value for that key.
	 * 
	 * @param key
	 */
	public void add(K key) {
		addKey(key);
	}

	/**
	 * Puts a new value, overwrites any prior value associated with the key's cluster.
	 * 
	 * 
	 * @param key
	 * @param value
	 */
	public void put(K key, V value) {
		Integer token = addKey(key);

		putWithoutNull(tokenToValue, token, value);
	}

	protected Integer addKey(K key) {
		Integer token = keyToToken.get(key);
		if(token == null) {
			token = gen.next(); // ++nextToken;
			keyToToken.put(key, token);
		}
		tokenToKeys.put(token, key);
		return token;
	}
	
	public V get(K key) {
		Integer token = keyToToken.get(key);
		V result = token == null
				? null
				: tokenToValue.get(token);
		
		return result;
	}


	public boolean isEqual(K a, K b) {
		Integer ta = keyToToken.get(a);
		boolean result = ta != null && ta.equals(keyToToken.get(b));
		return result;
	}
	
	@Override
	public String toString() {
		String result = "[";
		boolean isFirst = true;
		for(Entry> entry : tokenToKeys.asMap().entrySet()) {
			if(!isFirst) {
				result += ", ";
			}
			
			result += entry.getValue() + ": " + tokenToValue.get(entry.getKey());
			isFirst = false;
		}
		result += "]";
		return result;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy