package de.axone.data.weighted;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;
import de.axone.data.weighted.WeightedCollectionContained.WeightedItem;
public class AbstractWeightedCollectionContained<W extends WeightedCollectionContained<W, T>, T>
implements WeightedCollectionContained<W, T>, Iterable<WeightedItem<T>>, Serializable {
private HashMap<T,WeightedItem<T>> map = new HashMap<>();
@SuppressWarnings( "unchecked" )
private final W myself = (W)this;
private double sumWeight;
private double maxWeight;
private final Supplier<W> supplier;
private static final int CONSTANT_SEED =
(new Random()).nextInt();
public AbstractWeightedCollectionContained( Supplier<W> supplier ){
this.supplier = supplier;
}
@Override
public W supply() { return supplier.get(); }
@Override
public W addAll( Iterable<WeightedItem<T>> items ) {
for( WeightedItem<T> item : items ){
_add( item );
}
return myself;
}
@Override
public W addAll( Stream<WeightedItem<T>> items ) {
items.forEach( item -> {
_add( item );
} );
return myself;
}
@Override
public W addAllIf( Iterable<WeightedItem<T>> items, Predicate<T> filter ) {
for( WeightedItem<T> item : items ){
if( filter.test( item.item() ) ) _add( item );
}
return myself;
}
protected void _add( WeightedItem<T> item ) {
if( item == null ) return;
WeightedItemImpl old = (WeightedItemImpl) map.get( item );
double theWeight = item.weight();
if( old != null ) {
old.add( theWeight );
count( theWeight, old.weight() );
} else {
T theItem = item.item();
if( item instanceof AbstractWeightedCollectionContained.WeightedItemImpl ) {
map.put( theItem, item );
} else {
map.put( theItem, new WeightedItemImpl( theItem, theWeight ) );
}
count( theWeight, theWeight );
}
}
@Override
public W add( T item, double weight ) {
if( item == null ) return myself;
WeightedItemImpl old = (WeightedItemImpl) map.get( item );
if( old != null ){
old.add( weight );
count( weight, old.weight() );
} else {
map.put( item, new WeightedItemImpl( item, weight ) );
count( weight, weight );
}
return myself;
}
@Override
public boolean contains( T item ){ return map.containsKey( item ); }
@Override
public int size(){ return map.size(); }
@Override
public double weight() { return sumWeight; }
@Override
public double maxWeight() { return maxWeight; }
@Override
public double avgWeight() { return size() > 0 ? sumWeight / size() : 0; }
@Override
public Set<WeightedItem<T>> asSet() {
return new HashSet<>( map.values() );
}
@Override
public List<WeightedItem<T>> asList() {
return new ArrayList<>( map.values() );
}
@Override
public Stream<WeightedItem<T>> stream() {
return map.values().stream();
}
@Override
public Stream<WeightedItem<T>> sortedStream( Comparator<T> comparator ) {
return map.values().stream()
.sorted( new NestingComparator<>( comparator ) )
;
}
@Override
public Stream<WeightedItem<T>> bestStream() {
return map.values().stream()
.sorted( WEIGHT_COMPARATOR )
;
}
@Override
public W best( int amount ) {
return supplier.get()
.addAll( bestStream().limit( amount ) )
;
}
@Override
public W overrideBy( W other ) {
W result = supplier.get();
result.addAll( other );
result.addAllIf( this, i -> ! result.contains( i ) );
return result;
}
@Override
public List<WeightedItem<T>> shuffled() {
List<WeightedItem<T>> list = asList();
Collections.shuffle( list );
return list;
}
@Override
public List<WeightedItem<T>> shuffledStable() {
List<WeightedItem<T>> list = asList();
Collections.shuffle( list, new Random( CONSTANT_SEED ) );
return list;
}
@Override
public Iterator<WeightedItem<T>> iterator() {
return map.values().iterator();
}
private void count( double weight, double max ) {
sumWeight += weight;
if( max > maxWeight ) maxWeight = max;
}
@Override
public String toString() {
return String.format( "(S:%d/W:%.1f/M:%.1f/AVG:%.1f) %s",
size(), weight(), maxWeight(), avgWeight(), asList().toString() );
}
@Override
public int hashCode() {
return map.hashCode();
}
@Override
public boolean equals( Object obj ) {
if( this == obj ) return true;
if( obj == null ) return false;
if( !( obj instanceof AbstractWeightedCollectionContained ) ) return false;
AbstractWeightedCollectionContained<?,?> other = (AbstractWeightedCollectionContained<?,?>) obj;
if( map == null ) {
if( other.map != null )
return false;
} else if( !map.equals( other.map ) )
return false;
return true;
}
class WeightedItemImpl implements WeightedItem<T> {
private final T item;
private double weight;
WeightedItemImpl( T item, double weight ) {
this.item = item;
this.weight = weight;
}
@Override
public T item() {
return item;
}
@Override
public double weight() {
return weight;
}
@Override
public double normalized() {
return weight / maxWeight;
}
private void add( double weight ) {
this.weight += weight;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ( ( item == null ) ? 0 : item.hashCode() );
long temp;
temp = Double.doubleToLongBits( weight );
result = prime * result + (int) ( temp ^ ( temp >>> 32 ) );
return result;
}
@Override
public boolean equals( Object obj ) {
if( this == obj ) return true;
if( obj == null ) return false;
if( !( obj instanceof WeightedItem ) ) return false;
WeightedItem<?> other = (WeightedItem<?>) obj;
if( item == null ) {
if( other.item() != null )
return false;
} else if( !item.equals( other.item() ) )
return false;
if( Double.doubleToLongBits( weight ) != Double
.doubleToLongBits( other.weight() ) )
return false;
return true;
}
@Override
public String toString() {
return item.toString() +
'(' + weight() + '/' + normalized() + ')';
}
}
private static final class NestingComparator<T> implements Comparator<WeightedItem<T>> {
private final Comparator<T> nestedComparator;
public NestingComparator( Comparator<T> nestedComparator ) {
this.nestedComparator = nestedComparator;
}
@Override
public int compare( WeightedItem<T> o1, WeightedItem<T> o2 ) {
return nestedComparator.compare( o1.item(), o2.item() );
}
}
public static final Comparator<WeightedItem<?>> WEIGHT_COMPARATOR = new Comparator<WeightedItem<?>>() {
@Override
public int compare( WeightedItem<?> o1, WeightedItem<?> o2 ) {
return - Double.compare( o1.weight(), o2.weight() );
}
};
}