Is there an idiomatic way to take a Set<K> and a Function<K,V>, and get a Map<K,V> live view? (i.e. the Map is backed by the Set and Function combo, and if e.g. an element is added to the Set, then the corresponding entry also exists in the Map).
(see e.g. Collections2.filter for more discussion on live views)
What if a live view is not needed? Is there something better than this:
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;
}
Creating a Map from a Set and a Function
Here are two classes that should each do the job. The first just shows a map view of the set, while the second can write values back to the set through a special interface.
Call Syntax:
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);
Where to put this code?
Side note: If guava were my library, I'd make them accessible through the Maps class:
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);
Immutable version:
I have implemented this as a one-way view:
Changes to the set are reflected in
the map, but not vice-versa (and you can't change the map anyway, the put(key, value) method isn't implemented).
The entrySet() iterator uses the
set iterator internally, so it will
also inherit the internal iterator's
handling of
ConcurrentModificationException.
Both put(k,v) and
entrySet().iterator().remove() will
throw
UnsupportedOperationException.
Values are cached in a WeakHashMap,
with no special concurrency handling, i.e. there is no synchronization at
any level. This will do for most cases, but if your function is expensive, you might want to add some locking.
Code:
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;
}
}
Test:
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());
}
Output:
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
Mutable Version:
While I think it's a good idea to make this one-way, here's a version for Emil that provides a two-way view (it's a variation of Emil's variation of my solution :-)). It requires an extended map interface that I'll call ComputingMap to make clear that this is a map where it doesn't make sense to call put(key, value).
Map interface:
public interface ComputingMap<K, V> extends Map<K, V>{
boolean removeKey(final K key);
boolean addKey(final K key);
}
Map implementation:
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;
}
}
Test:
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);
}
Output:
{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}
Caution. Sean Patrick Floyd's answer, although very useful, has a flaw. A simple one, but took me a while to debug so don't fall in the same trap: the MapEntry class requires equals and hashcode implementations. Here are mine (simple copy from the 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());
}
This reply would be better as a commentary to the relevant answer, but AFAIU I don't have the right to post a comment (or did't find how to!).
Guava 14 now has Maps.asMap for a view of the Set and Maps.toMap for an immutable copy.
You can see much of the discussion of the issues involved here:
https://github.com/google/guava/issues/56
For the non live view the code exists in lambdaJ with 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);
I tried my own implementation : http://ideone.com/Kkpcn
As said in the comments, I have to extends another class so I just implemented Map, that's why there is so much code.
There is a totally useless (or not ?) feature that allows you to change the converter on the fly.
what about Maps.uniqueIndex()
I don't know if this is what you mean by live view.Any way here is my try.
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();
}
};
}
}
The implementation is not complete and the overridden functions are not tested but I hope it convey's the idea.
UPDATE:
I made some small change's to seanizer's answer so that the changes made in map will reflect in the set also.
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);
}
}
Interface SetFunctionMap:
public interface SetFunctionMap<K,V> extends Map<K, V>{
public boolean putKey(K key);
public boolean removeKey(K key);
}
Test Code:
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);
}
}
Output:
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
Related
Gives "non-static variable this cannot be referenced from a static context"
This is the code below for template class
public class BiHashMap<K1, K2, V>{
private final Map<K1, Map<K2, V>> mMap;
public BiHashMap() {
mMap = new HashMap<K1, Map<K2, V>>();
}
public V put(K1 key1, K2 key2, V value) {
Map<K2, V> map;
if (mMap.containsKey(key1)) {
map = mMap.get(key1);
} else {
map = new HashMap<K2, V>();
mMap.put(key1, map);
}
return map.put(key2, value);
}
}
public static void main(String[] args) {
BiHashMap<double,double,double> table1 = new BiHashMap<double,double,double>();
table1.put(0.375,1,350);
I tried making a new class for double but the error remained
public class dbble{
double number;
dbble(double x){
number=x;
}
}
I have test your code above and edit it to make it run: (You have to put the main function inside the class, you need to use wrapper type for double, and cast the value you put to when you call the function).
public class BiHashMap<K1, K2, V> {
private final Map<K1, Map<K2, V>> mMap;
public BiHashMap() {
mMap = new HashMap<K1, Map<K2, V>>();
}
public V put(K1 key1, K2 key2, V value) {
Map<K2, V> map;
if (mMap.containsKey(key1)) {
map = mMap.get(key1);
} else {
map = new HashMap<K2, V>();
mMap.put(key1, map);
}
return map.put(key2, value);
}
public static void main(String[] args) {
BiHashMap<Double, Double, Double> table1 = new BiHashMap<Double, Double, Double>();
table1.put(0.375, Double.valueOf(1), Double.valueOf(350));
}
}
I'm dealing with some data like the following, the first column is the trade id, the second column is the simulation id(duplicate a lot), the third column is some stupid date also quite duplicated, the forth one is the present value of a trade, mostly it is just 0, but any other value should be quite unique.
41120634|1554|20150203|-509057.56
40998001|1554|20150203|0
40960705|1554|20150203|0
40998049|1554|20150203|0
41038826|1554|20150203|0
41081136|1554|20150203|-7198152.23
41120653|1554|20150203|-319.436349
41081091|1554|20150203|-4.28520907E+009
I've decided to use a data structure as follows:
Map<Integer,Map<Integer,List<Map<Integer,Float>>>
Then my csv will be saved like:
{20150203:{1554:[{41120634:-509057.56,41120634:0,...}]}}
My question is how to convert such a csv file to my intended data structure efficiently?
Easy to implement would be a structure like Map<K1, Map<K2, Map<K3, V>>>. This format is called NestedMap, in this case a NestedMap3, three keys and one value.
It is very easy to implement using inheritance since a NestedMap3 internally uses a Map<K1, NestedMap2<K2, K3, V>> and the NestedMap2 uses a Map<K1, Map<K2, V>>.
Next you should think about where to use a Map and where to use a multiple container like Pair<A, B>, Triple<A, B, C>, etc.
If your values change frequently, use a container like Pair. If your values are quite often the same, use a Map. Based on this you may mix some values, for example a Map<K, Triple<A, B, C>> might be good if the later values change frequently.
In your provided scenario the second and third value are often the same. So I suggest to use a NestedMap3<Integer, Integer, Integer, Float> in your case.
First the code to setup the data structure, I'll assume your input are lines given as String stored in inputLines:
NestedMap3<Integer, Integer, Integer, Float> map = new NestedMap3<>();
for (String line : inputLines) {
String[] values = inputLines.split("|");
map.put(toInt(values[0]), toInt(values[1]), toInt(values[2]), toFloat(values[3]));
}
Of course we also need to implement toInt and toFloat:
public Integer toInt(final String value) {
return Integer.parseInt(value);
}
public Float toFloat(final String value) {
return Float.parseFloat(value);
}
And finally the implementation of NestedMap3 and NestedMap2:
public class NestedMap3<K1, K2, K3, V> {
private final Map<K1, NestedMap2<K2, K3, V>> mK1ToK2ToK3V =
new HashMap<K1, NestedMap2<K2, K3, V>>();
public V put(K1 key1, K2 key2, K3 key3, V value) {
NestedMap2<K2, K3, V> k2tok3toV = mK1ToK2ToK3V.get(key1);
if (k2tok3toV == null) {
k2tok3toV = new NestedMap2<>();
mK1ToK2ToK3V.put(key1, k2tok3toV);
}
return k2tok3toV.put(key2, key3, value);
}
public V get(K1 key1, K2 key2, K3 key3) {
final NestedMap2<K2, K3, V> k2tok3toV = mK1ToK2ToK3V.get(key1);
if (k2tok3toV == null) {
return null;
} else {
return k2tok3toV.get(key2, key3);
}
}
public Map<K3, V> get(K1 key1, K2 key2) {
final NestedMap2<K2, K3, V> k2toV = mK1ToK2ToK3V.get(key1);
if (k2toV == null) {
return null;
} else {
return k2toV.get(key2);
}
}
public NestedMap2<K2, K3, V> get(K1 key1) {
return mK1ToK2ToK3V.get(key1);
}
public Set<K1> keySet() {
return mK1ToK2ToK3V.keySet();
}
public void clear() {
mK1ToK2ToK3V.clear();
}
}
public class NestedMap2<K1, K2, V> {
private final Map<K1, Map<K2, V>> mK1ToK2ToV = new HashMap<K1, Map<K2, V>>();
public V put(K1 key1, K2 key2, V value) {
Map<K2, V> k2toV = mK1ToK2ToV.get(key1);
if (k2toV == null) {
k2toV = new HashMap<>();
mK1ToK2ToV.put(key1, k2toV);
}
return k2toV.put(key2, value);
}
public V get(K1 key1, K2 key2) {
final Map<K2, V> k2toV = mK1ToK2ToV.get(key1);
if (k2toV == null) {
return null;
} else {
return k2toV.get(key2);
}
}
public Map<K2,V> get(K1 key1) {
return mK1ToK2ToV.get(key1);
}
public Set<K1> keySet() {
return mK1ToK2ToV.keySet();
}
public Iterable<Pair<K1,K2>> keys2() {
return new Iterable<Pair<K1,K2>>() {
#Override
public Iterator<Pair<K1, K2>> iterator() {
return new Iterator<Pair<K1,K2>>() {
private Iterator<Entry<K1, Map<K2, V>>> mIterator1;
private Entry<K1, Map<K2, V>> mIterator1Object;
private Iterator<K2> mIterator2;
{
mIterator1 = mK1ToK2ToV.entrySet().iterator();
if (mIterator1.hasNext()) {
mIterator1Object = mIterator1.next();
mIterator2 = mIterator1Object.getValue().keySet().iterator();
}
}
#Override
public boolean hasNext() {
if (mIterator1Object == null) {
return false;
} else {
return mIterator2.hasNext();
}
}
#Override
public Pair<K1, K2> next() {
if (mIterator1Object == null) {
throw new NoSuchElementException();
} else {
if (!mIterator2.hasNext()) {
if (!mIterator1.hasNext()) {
throw new NoSuchElementException();
} else {
mIterator1Object = mIterator1.next();
assert mIterator1Object.getValue().size() > 0 : "must contain at least one value";
mIterator2 = mIterator1Object.getValue().keySet().iterator();
}
}
return new Pair<K1, K2>(mIterator1Object.getKey(), mIterator2.next());
}
}
};
}
};
}
public Iterable<Triple<K1,K2,V>> entrySet() {
final ArrayList<Triple<K1,K2,V>> result = new ArrayList<Triple<K1,K2,V>>();
for (final Entry<K1, Map<K2, V>> entryOuter : mK1ToK2ToV.entrySet()) {
for (final Entry<K2, V> entryInner : entryOuter.getValue().entrySet()) {
result.add(new Triple<>(entryOuter.getKey(), entryInner.getKey(), entryInner.getValue()));
}
}
return result;
}
public void addAll(NestedMap2<K1, K2, V> nestedMap) {
for (final Triple<K1, K2, V> triple : nestedMap.entrySet()) {
this.put(triple.getFirst(), triple.getSecond(), triple.getThird());
}
}
public Map<K2, V> remove(K1 k1) {
return mK1ToK2ToV.remove(k1);
}
public V remove(K1 k1, K2 k2) {
final Map<K2, V> k2ToV = mK1ToK2ToV.get(k1);
if (k2ToV == null) {
return null;
} else {
return k2ToV.remove(k2);
}
}
#Override
public String toString() {
return mK1ToK2ToV.toString();
}
public void clear() {
mK1ToK2ToV.clear();
}
}
First, create an Object that's suitable for your data, after use CSV methods from CommonApache.
https://commons.apache.org/proper/commons-csv/
Below I found a extract from a code I did and I think it will be useful to you.
public class csvToArray {
public ArrayList<data> csvTo_data() throws FileNotFoundException, IOException {
// Array to receive parser
ArrayList<data> your_data = new ArrayList<data>();
// data Object to receive the CSV data
data yourData = new data();
// call open file
OpenFile of = new OpenFile();
// get the files in a array of files
File[] files = of.chosefile();
// count number of files
int size = files.length;
for (int i = 0; i < size; i++) {
// CSV Parser can receive FileReader object, so I sent the path name
CSVParser parser = new CSVParser(new FileReader(files[i].getAbsolutePath()),
CSVFormat.DEFAULT.withSkipHeaderRecord());
System.out.println("You chose to open this file:" + files[i].getName());
// iterate to pass from CSV tyoe to Object data type
for (CSVRecord s : parser) {
String dataName = s.get(0);
String dataType = s.get(1);
int dataSize = Integer.parseInt(s.get(2));
// get the data from file's path name
int date = Integer.parseInt(files[i].getName().substring(3, 7));
yourData = new data(dataName , dataType , dataSize, date);
your_data.add(yourData);
}
parser.close();
}
return your_data;
I need a HashMap that
(1) matches keys by Object reference, and
(2) maintains insertion order when iterating
These functionalities are implemented in IdentityHashMap and LinkedHashMap separately.
Is there any way to get a data structure that suits my need? Either one that is present in Java standard OR 3rd party libraries (like Guava), OR by using some trick on LinkedHashMap maybe, so that it uses object reference for matching keys?
You can use Guava's Equivalence for this:
Equivalence<Object> equivalence = Equivalence.identity();
Map<Equivalence.Wrapper<Object>, Object> map = new LinkedHashMap<>();
map.put(equivalence.wrap(a), b);
It interested me, so I wrote an implementation.
public class IdentityLinkedHashMap<K, T> extends AbstractMap<K,T> {
static Equivalence<Object> equivalence = Equivalence.identity();
private IdentityLinkedHashSet set = new IdentityLinkedHashSet();
#Override
public Set<Entry<K, T>> entrySet() {
return set;
}
#Override
public T put(K k, T t) {
return set.innerMap.put( equivalence.wrap(k), t);
}
#Override
public boolean containsKey(Object arg0) {
return set.contains(arg0);
}
#Override
public T remove(Object arg0) {
return set.innerMap.remove(equivalence.wrap(arg0));
}
#Override
public T get(Object arg0) {
return set.innerMap.get(equivalence.wrap(arg0));
}
public class MyEntry implements Entry<K, T> {
final Entry<Equivalence.Wrapper<K>, T> entry;
public MyEntry(Entry<Wrapper<K>, T> entry) {
this.entry = entry;
}
#Override
public K getKey() {
return entry.getKey().get();
}
#Override
public T getValue() {
return entry.getValue();
}
#Override
public T setValue(T value) {
return entry.setValue(value);
}
}
public class IdentityLinkedHashSet extends AbstractSet<Entry<K,T>> {
Map<Equivalence.Wrapper<K>, T> innerMap = new LinkedHashMap<>();
#Override
public Iterator<Entry<K, T>> iterator() {
return Iterators.transform(innerMap.entrySet().iterator(), entry -> new MyEntry(entry));
}
#Override
public boolean add(Entry<K, T> entry) {
Wrapper<K> wrap = equivalence.wrap(entry.getKey());
innerMap.put(wrap, entry.getValue());
return true;
}
#Override
public int size() {
return innerMap.size();
}
#Override
public boolean contains(Object arg0) {
return innerMap.containsKey(equivalence.wrap(arg0));
}
}
Is there some trick to convert HashMultiset<String> to Map<String,Integer>, except from iterating all the entries in the Set?
Update: The Integer should represent the count of String in the multiset.
You can use Maps.asMap. With lambda expression (Java 8) it will be a one-liner:
Maps.asMap(multiset.elementSet(), elem -> multiset.count(elem));
In Java 7 and below:
final Multiset<String> multiset = HashMultiset.create();
Map<String, Integer> freqMap = Maps.asMap(multiset.elementSet(),
new Function<String, Integer>() {
#Override
public Integer apply(String elem) {
return multiset.count(elem);
}
});
Updated to java 8, here is what I found as the best answer (based on other answers):
public static <E> Map<E, Integer> convert(Multiset<E> multiset) {
return multiset.entrySet().stream().collect(
Collectors.toMap(x->x.getElement(),x->x.getCount()));
}
or:
public static <E> Map<E, Integer> convert(Multiset<E> multiset) {
return multiset.entrySet().stream().collect(
Collectors.toMap(Entry::getElement,Entry::getCount));
}
With Eclipse Collections you can use the method toMapOfItemToCount on a Bag (aka Multiset), which will return a Map with a key of the same type in the Bag and an Integer count.
Note: I am a committer for Eclipse collections.
You could simply loop through the entries and put the element and count to a map.
public class MultisetToMap {
public static <E> Map<E, Integer> convert(Multiset<E> multiset) {
Map<E, Integer> map = Maps.newHashMap();
for (E e : multiset) {
multiset.count(e);
map.put(e, multiset.count(e));
}
return map;
}
}
Below is the (passing) JUnit test.
#Test
public void testConvert() {
HashMultiset<String> hashMultiset = HashMultiset.create();
hashMultiset.add("a");
hashMultiset.add("a");
hashMultiset.add("a");
hashMultiset.add("b");
hashMultiset.add("c");
Map<String, Integer> map = MultisetToMap.convert(hashMultiset);
assertEquals((Integer) 3, map.get("a"));
assertEquals((Integer) 1, map.get("b"));
assertEquals((Integer) 1, map.get("c"));
}
If you really want to avoid looping through the entries of the Multiset, you can create a view of it as a Map:
public class MultisetMapView<E> implements Map<E, Integer> {
private Multiset<E> delegate;
public MultisetMapView(Multiset<E> delegate) {
this.delegate = delegate;
}
public int size() {
return delegate.size();
}
public boolean isEmpty() {
return delegate.isEmpty();
}
public boolean containsKey(Object key) {
return delegate.contains(key);
}
public boolean containsValue(Object value) {
throw new UnsupportedOperationException();
}
public Integer get(Object key) {
return delegate.count(key);
}
public Integer put(E key, Integer value) {
return delegate.setCount(key, value);
}
public Integer remove(Object key) {
int count = delegate.count(key);
delegate.remove(key);
return count;
}
public void putAll(Map<? extends E, ? extends Integer> m) {
for (Entry<? extends E, ? extends Integer> entry : m.entrySet()) {
delegate.setCount(entry.getKey(), entry.getValue());
}
}
public void clear() {
delegate.clear();
}
public Set<E> keySet() {
return delegate.elementSet();
}
public Collection<Integer> values() {
throw new UnsupportedOperationException();
}
public Set<java.util.Map.Entry<E, Integer>> entrySet() {
Set<java.util.Map.Entry<E, Integer>> entrySet = Sets.newHashSet();
for (E e : delegate) {
delegate.count(e);
entrySet.add(Maps.immutableEntry(e, delegate.count(e)));
}
return entrySet;
}
}
In my implementation, I declined to implement the containsValue and values methods, as these are not useful in the context. If desired, these could be implemented by looping through the entries and inspecting the count of the elements encountered.
And again, you can see this working in this JUnit case:
#Test
public void testConvert() {
HashMultiset<String> hashMultiset = HashMultiset.create();
hashMultiset.add("a");
hashMultiset.add("a");
hashMultiset.add("a");
hashMultiset.add("b");
hashMultiset.add("c");
Map<String, Integer> map = new MultisetMapView<String>(hashMultiset);
assertEquals((Integer) 3, map.get("a"));
assertEquals((Integer) 1, map.get("b"));
assertEquals((Integer) 1, map.get("c"));
}
This is possible, but only with reflection and it looks very unsafe.
HashMultiset<String> hashMultiset = HashMultiset.create();
hashMultiset.add("a");
hashMultiset.add("a");
hashMultiset.add("a");
hashMultiset.add("b");
hashMultiset.add("c");
System.out.println(hashMultiset);
Method method = hashMultiset.getClass().getSuperclass().getDeclaredMethod("backingMap");
method.setAccessible(true);
Map<String, Integer> map = (Map<String, Integer>) method.invoke(hashMultiset);
System.out.println(map);
Result:
[b, c, a x 3]
{b=1, c=1, a=3}
How can I construct a SortedMap on top of Guava's computing map (or vice versa)? I want the sorted map keys as well as computing values on-the-fly.
The simplest is probably to use a ConcurrentSkipListMap and the memoizer idiom (see JCiP), rather than relying on the pre-built unsorted types from MapMaker. An example that you could use as a basis is a decorator implementation.
May be you can do something like this.It's not a complete implementation.Just a sample to convey the idea.
public class SortedComputingMap<K, V> extends TreeMap<K, V> {
private Function<K, V> function;
private int maxSize;
public SortedComputingMap(int maxSize, Function<K, V> function) {
this.function = function;
this.maxSize = maxSize;
}
#Override
public V put(K key, V value) {
throw new UnsupportedOperationException();
}
#Override
public void putAll(Map<? extends K, ? extends V> map) {
throw new UnsupportedOperationException();
}
#Override
public V get(Object key) {
V tmp = null;
K Key = (K) key;
if ((tmp = super.get(key)) == null) {
super.put(Key, function.apply(Key));
}
if (size() > maxSize)
pollFirstEntry();
return tmp;
}
public static void main(String[] args) {
Map<Integer, Long> sortedMap = new SortedComputingMap<Integer, Long>(3,
new Function<Integer, Long>() {
#Override
public Long apply(Integer n) {
Long fact = 1l;
while (n != 0)
fact *= n--;
return fact;
}
});
sortedMap.get(12);
sortedMap.get(1);
sortedMap.get(2);
sortedMap.get(5);
System.out.println(sortedMap.entrySet());
}
}
If you need the thread safety, this could be tricky, but if you don't I'd recommend something close to Emil's suggestion, but using a ForwardingSortedMap rather than extending TreeMap directly.