I would like to check how to implement a custom Collector.
Say, I have a need to do some
(1) analysis on words like alphabet-frequency map and
(2) ability to combine 2 results to get a single result.
class CharHistogram implements Collector<String, Map<Character, Integer>, Map<Character, Integer>> {
public static CharHistogram toCharHistogram(){
return new CharHistogram();
}
#Override
public Supplier<Map<Character, Integer>> supplier() {
SysOut.print("supplier invoked");
return HashMap::new;
}
#Override
public BiConsumer<Map<Character, Integer>, String> accumulator() {
SysOut.print("accumulator invoked");
return (map, val) -> {
SysOut.print(val +" processed");
char[] characters = val.toCharArray();
for (char character : characters) {
int count = 1;
if (map.containsKey(character)) {
count = map.get(character);
count++;
}
map.put(character, count);
}
};
}
#Override
public BinaryOperator<Map<Character, Integer>> combiner() {
SysOut.print("combiner invoked");
return (map1, map2) -> {
SysOut.print(map1+" merged to "+map2);
map2.forEach((k, v) -> map1.merge(k, v, (v1, v2) -> v1 + v2));
return map1;
};
}
#Override
public Function<Map<Character, Integer>, Map<Character, Integer>> finisher() {
SysOut.print("finisher invoked");
return Function.identity();
}
#Override
public Set<java.util.stream.Collector.Characteristics> characteristics() {
return Collections.unmodifiableSet(EnumSet.of(Characteristics.IDENTITY_FINISH, Characteristics.UNORDERED));
}
}
Client code:
CharHistogram charStatsState = CharHistogram.toCharHistogram();
Map<Character, Integer> charCountMap = Arrays.asList("apple","orange","orange").stream().collect(charStatsState);
SysOut.print(charCountMap);
charCountMap = Arrays.asList("pears","pears","orange").stream().collect(charStatsState);
SysOut.print(charCountMap);
Output:
[main]: supplier invoked
[main]: accumulator invoked
[main]: combiner invoked
[main]: apple processed
[main]: orange processed
[main]: orange processed
[main]: {p=2, a=3, r=2, e=3, g=2, l=1, n=2, o=2}
[main]: supplier invoked
[main]: accumulator invoked
[main]: combiner invoked
[main]: pears processed
[main]: pears processed
[main]: orange processed
[main]: {p=2, a=3, r=3, s=2, e=3, g=1, n=1, o=1}
I don't see the combiner nor the finisher getting called and I believe these needs to be designed correctly to achieve what I'm looking for.
What am I missing?
EDIT:
A possible approach to support streams and combiner. The below code doesn't work though.
class CharStreamHistogram implements Function<String, Map<Character, Integer>>{
private int totalCharactersRead;
private Map<Character, Integer> histogram;
public int getTotalCharactersRead() {
return totalCharactersRead;
}
public Map<Character, Integer> getHistogram() {
return histogram;
}
public void setHistogram(Map<Character, Integer> histogram) {
this.histogram = histogram;
}
public void setTotalCharactersRead(int totalCharactersRead) {
this.totalCharactersRead = totalCharactersRead;
}
public Map<Character, Integer> combine(Map<Character, Integer> map2) {
Map<Character, Integer> map1 = this.histogram;
map2.forEach((k, v) -> map1.merge(k, v, (v1, v2) -> v1 + v2));
return map2;
}
#Override
public Map<Character, Integer> apply(String val) {
char[] characters = val.toCharArray();
totalCharactersRead += characters.length;
for (char character : characters) {
int count = 1;
if (histogram.containsKey(character)) {
count = histogram.get(character);
count++;
}
histogram.put(character, count);
}
return histogram;
}
}
public static <T> Collector<T, ?, CharStreamHistogram> summarizeCharStream(
CharStreamHistogram histogram) { //TODO: is this correct?
Collector charStatsState = new Collector<String, CharStreamHistogram, CharStreamHistogram>() {
#Override
public Supplier<CharStreamHistogram> supplier() {
return CharStreamHistogram::new;
}
#Override
public BiConsumer<CharStreamHistogram, String> accumulator() {
//TODO: What to do here?
return null;
}
#Override
public BinaryOperator<CharStreamHistogram> combiner() {
BinaryOperator binaryOperator = (l, r) -> {
l.combine(r); //TODO: Something like this?
};
return binaryOperator;
}
#Override
public Function<CharStreamHistogram, CharStreamHistogram> finisher() {
//TODO: What to do here?
return null;
}
#Override
public Set<java.util.stream.Collector.Characteristics> characteristics() {
return Collections.unmodifiableSet(EnumSet.of(Characteristics.UNORDERED));
}
};
return charStatsState;
}
Well you have declared Characteristics.IDENTITY_FINISH - which explicitly means that finisher will not be called, and the combiner will be called only in case of a parallel stream.
Related
I have a List of Map<String, String> that I want to iterate over and find the common elements inside the map of string and add to another map.
I am confused what should go inside the if loop to get my expected output. I am looking for comparator type call but I couldn't find that anywhere.
for (int i = 0; i < list.size() - 1; i++) {
if (list.get(i).get("Journal ID").equals(list.get(i+1).get("Journal ID")))
// ???
}
}
I was using this method to sort list of Maps. I am expecting some thing like this
public Comparator<Map<String, String>> mapComparator = new Comparator<>() {
public int compare(Map<String, String> m1, Map<String, String> m2) {
return m1.get("Journal ID").compareTo(m2.get("Journal ID"));
}
}
Collections.sort(list, mapComparator);
// input and the expected output
my List = [{Journal ID=123, featureID=312},{Journal ID=123, featureID=313},{Journal ID=134,
featureID=314},{Journal ID=123, featureID=1255}]
expected output is one that matching the "Journal ID" [{Journal ID=123, featureID=312},
{ Journal ID=123, featureID=313},{Journal ID=123, featureID=1255}].
One approach is to construct a second map which will aggregate all maps.
It will reflect all keys form all maps. As value will be a list of each key value with counters. Implementation can be enhanced also, but the main aspect is how to proceed. Having the aggregate map, then is straight forward to transform in what ever structure needed.
public class TestEqMap {
public static void main(String[] args)
{
Map<String, String> m1 = Map.of("a","a1","b","b1");
Map<String, String> m2 = Map.of("a","a1","b","b2");
Map<String, String> m3 = Map.of("a","a2","b","b2");
Map<String, String> m4 = Map.of("a","a1","b","b2");
Map<String, String> m5 = Map.of("a","a3","b","b2");
AggMap amap = new AggMap();
amap.addMap(m1);
amap.addMap(m2);
amap.addMap(m3);
amap.addMap(m4);
amap.addMap(m5);
amap.map.forEach((k,v)->System.out.println("key="+k+"\n"+v));
}
static class AggMap
{
public Map<String, ListItem> map = new HashMap<String,ListItem>();
public void addMap(Map<String,String> m)
{
for(String key: m.keySet())
{
if(this.map.containsKey(key))
{
this.map.get(key).addItem(m.get(key));
}
else
{
ListItem li = new ListItem();
li.addItem(m.get(key));
this.map.put(key, li);
}
}
}
}
static class ListItem
{
public List<Item> li = new ArrayList<Item>();
public ListItem() {};
public void addItem(String str)
{
for(Item i: this.li)
{
if(i.val.equals(str))
{
i.count++;
return;
}
}
this.li.add(new Item(str));
}
public String toString()
{
StringBuffer sb= new StringBuffer();
this.li.forEach(i->sb.append(i+"\n"));
return sb.toString();
}
}
static class Item
{
public String val;
public int count=1;
public Item(String val)
{
this.val = val;
}
public String toString()
{
return "val="+this.val+" count="+this.count;
}
}
}
Output:
key=a
val=a1 count=3
val=a2 count=1
val=a3 count=1
key=b
val=b1 count=1
val=b2 count=4
I have a problem with my custom iterator, so I'm asking for your help. I have class MyIterator, which is an iterator with transformation. This class has methods:
next() - returns next element
hasNext() - check if next element exists
fromIterator - static method, which converts Iterator to MyIterator
map - method which takes functional interface and returns MyIterator with transformation rule corresponding to this interface
forEach - method which takes functional interface and iterates over all remaining objects according to the interface. My realisation is
import java.util.Iterator;
import java.util.function.Consumer;
import java.util.function.Function;
public class MyIterator<K, V> {
private final Iterator<K> iterator;
private final Function<K, V> function;
#SuppressWarnings("unchecked")
public static <K, V> MyIterator<K, V> fromIterator(Iterator<K> iterator) {
return new MyIterator<>(iterator, k -> (V) k);
}
private MyIterator(Iterator<K> iterator, Function<K, V> function) {
this.iterator = iterator;
this.function = function;
}
public V next() {
return this.function.apply(iterator.next());
}
public boolean hasNext() {
return this.iterator.hasNext();
}
public MyIterator<K, V> map(Function<K, V> function) {
return new MyIterator<K, V>(this.iterator, this.function);
}
public void forEach(Consumer<V> action) {
while (hasNext()) {
action.accept(this.next());
}
}
}
So, I did this task, but I can't understand, how to change the method map into chaining method (pipeline). I mean the following:
MyIterator<String, Integer> myIterator3 = MyIterator.fromIterator(stringsArray.iterator()).map(s -> s.length()).map(i -> i.toString()).map(s -> s.length());
For example, I have String "England". After first map I want to get 7 ("England" consists of 7 characters), than "7", than 1 (because String "7" consists of 1 character). My assumption is that I should use methods andThen/compose in my method map, by I can't understand, how.
As Iterator accepts one parameter, here is how I modified your code.
public class ChainedIterator<T> {
private Function<T, ?> action;
private ChainedIterator<T> chain;
private final Iterator<?> iterator;
private <R> ChainedIterator(Iterator<?> iterator, Function<T, R> action, ChainedIterator<T> prev) {
this.action = action;
this.chain = prev;
this.iterator = iterator;
}
public static <T> ChainedIterator<T> fromIterator(Iterator<T> iterator) {
return new ChainedIterator<>(iterator, Function.identity(), null);
}
public T next() {
return (T) this.action.apply((T) (Objects.nonNull(this.chain) ? this.chain.next() : this.iterator.next()));
}
public boolean hasNext() {
return this.iterator.hasNext();
}
public <R> ChainedIterator<R> map(Function<T, R> action) {
return new ChainedIterator(this.iterator, action, this);
}
public void forEach(Consumer<T> action) {
while (hasNext()) {
action.accept(this.next());
}
}
}
Usage Example
Iterator<String> stringIterator = Arrays.asList("England", "India").iterator();
ChainedIterator<Integer> iterator = ChainedIterator.fromIterator(stringIterator)
.map(s -> s.length())
.map(i -> String.valueOf(i))
.map(s -> s.length());
I hope this helps :)
Update your custom Iterator and allow fromIterator() takes function rather you define it
public class MyIterator<K, V> {
private Iterator<K> iterator;
private List<Function<K, ?>> functions;
public static <K, V> MyIterator<K, V> fromIterator(Iterator<K> iterator) {
return new MyIterator<>(iterator);
}
private MyIterator(Iterator<K> iterator) {
this.iterator = iterator;
functions = new ArrayList<>();
}
private MyIterator(Iterator<K> iterator, Function<K, ?> function) {
this.iterator = iterator;
functions = new ArrayList<>();
functions.add(function);
}
private MyIterator(Iterator<K> iterator, List<Function<K, ?>> functions) {
this.iterator = iterator;
this.functions = functions;
}
public Object next() {
K key = iterator.next();
Object val = null;
for (int i = 0; i < functions.size(); i++) {
val = functions.get(i).apply(key);
key = (K) val;
}
return val;
}
public boolean hasNext() {
return iterator.hasNext();
}
public <R, RR> MyIterator<R, RR> map(Function<K, R> function) {
List<Function<K, ?>> functions2 = this.functions;
functions2.add(function);
return new MyIterator(iterator, functions2);
}
public void forEach(Consumer<Object> action) {
while (hasNext()) {
action.accept(next());
}
}
}
, main
public static void main(String[] args) throws Exception {
Iterator<String> sIterator = Arrays.asList("aaa", "bbbb", "cccc", "ddddd").iterator();
MyIterator.<String, Object>fromIterator(sIterator).map(s -> s.length()).map(i -> i + "")
.map(str -> str.length()).forEach(System.out::println);
}
, output
1
1
1
1
Consider the simple class Foo:
public class Foo {
public Float v1;
public Float v2;
public String name;
public Foo(String name, Float v1, Float v2) {
this.name = name;
this.v1 = v1;
this.v2 = v2;
}
public String getName() {
return name;
}
}
Now, I have a collection of Foos and I'd like to group them by Foo::getName. I wrote a custom Collector to do that but it doesn't seem to work as expected. More precisely, combiner() never gets called. Why?
public class Main {
public static void main(String[] args) {
List<Foo> foos = new ArrayList<>();
foos.add(new Foo("blue", 2f, 2f));
foos.add(new Foo("blue", 2f, 3f));
foos.add(new Foo("green", 3f, 4f));
Map<String, Float> fooGroups = foos.stream().collect(Collectors.groupingBy(Foo::getName, new FooCollector()));
System.out.println(fooGroups);
}
private static class FooCollector implements Collector<Foo, Float, Float> {
#Override
public Supplier<Float> supplier() {
return () -> new Float(0);
}
#Override
public BiConsumer<Float, Foo> accumulator() {
return (v, foo) -> v += foo.v1 * foo.v2;
}
#Override
public BinaryOperator<Float> combiner() {
return (v1, v2) -> v1 + v2;
}
#Override
public Function<Float, Float> finisher() {
return Function.identity();
}
#Override
public Set<Characteristics> characteristics() {
Set<Characteristics> characteristics = new TreeSet<>();
return characteristics;
}
}
}
First, the combiner function does not need to get called if you aren't using multiple threads (parallel stream). The combiner gets called to combine the results of the operation on chunks of your stream. There is no parallelism here so the combiner doesn't need to be called.
You are getting zero values because of your accumulator function. The expression
v += foo.v1 * foo.v2;
will replace v with a new Float object. The original accumulator object is not modified; it is still 0f. Besides, Float, like other numeric wrapper types (and String) is immutable and cannot be changed.
You need some other kind of accumulator object that is mutable.
class FloatAcc {
private Float total;
public FloatAcc(Float initial) {
total = initial;
}
public void accumulate(Float item) {
total += item;
}
public Float get() {
return total;
}
}
Then you can modify your custom Collector to use FloatAcc. Supply a new FloatAcc, call accumulate in the accumulator function, etc.
class FooCollector implements Collector<Foo, FloatAcc, Float> {
#Override
public Supplier<FloatAcc> supplier() {
return () -> new FloatAcc(0f);
}
#Override
public BiConsumer<FloatAcc, Foo> accumulator() {
return (v, foo) -> v.accumulate(foo.v1 * foo.v2);
}
#Override
public BinaryOperator<FloatAcc> combiner() {
return (v1, v2) -> {
v1.accumulate(v2.get());
return v1;
};
}
#Override
public Function<FloatAcc, Float> finisher() {
return FloatAcc::get;
}
#Override
public Set<Characteristics> characteristics() {
Set<Characteristics> characteristics = new TreeSet<>();
return characteristics;
}
}
With these changes I get what you're expecting:
{green=12.0, blue=10.0}
You have an explanation as to why the current collector does not work from rgettman.
It is worth checking to see what helper methods exist to create custom collectors. For example, this entire collector can be defined far more concisely as:
reducing(0.f, v -> v.v1 * v.v2, (a, b) -> a + b)
It is not always possible to use methods like these; but the conciseness (and, presumably, the well-testedness) should make them the first choice when possible.
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}
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