package tc.oc.pgm.compose;
import java.util.stream.Stream;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Range;
import tc.oc.commons.core.inspect.Inspectable;
import tc.oc.commons.core.random.AdvancingEntropy;
import tc.oc.commons.core.random.Entropy;
import tc.oc.commons.core.random.MutableWeightedRandomChooser;
import tc.oc.commons.core.stream.Collectors;
import tc.oc.commons.core.util.Ranges;
import tc.oc.pgm.filters.Filter;
import tc.oc.pgm.filters.query.ITransientQuery;
public class Any<T> extends CompositionImpl<T> {
public static class Option<T> extends Inspectable.Impl {
@Inspect private final double weight;
@Inspect private final Filter filter;
@Inspect private final Composition<T> element;
public Option(double weight, Filter filter, Composition<T> element) {
this.weight = weight;
this.filter = filter;
this.element = element;
}
}
@Inspect private final Range<Integer> count;
@Inspect private final boolean unique;
@Inspect private final ImmutableList<Option<T>> options;
private final double totalWeight;
public Any(Range<Integer> count, boolean unique, Stream<Option<T>> choices) {
this(count, unique, choices.collect(Collectors.toImmutableList()));
}
public Any(Range<Integer> count, boolean unique, Iterable<Option<T>> choices) {
this.count = Ranges.toClosed(count);
this.unique = unique;
this.options = ImmutableList.copyOf(choices);
this.totalWeight = this.options.stream().mapToDouble(c -> c.weight).sum();
}
@Override
public boolean isConstant() {
return false;
}
@Override
public Stream<T> dependencies() {
return options.stream().flatMap(option -> option.element.dependencies());
}
@Override
public Stream<T> elements(ITransientQuery query) {
if(totalWeight <= 0) return Stream.empty();
final MutableWeightedRandomChooser<Option<T>, Double> chooser = new MutableWeightedRandomChooser<>();
for(Option<T> option : options) {
if(option.filter.query(query).isAllowed()) {
chooser.add(option, option.weight);
}
}
final Entropy entropy = new AdvancingEntropy(query.entropy().randomLong());
Stream<T> result = Stream.empty();
for(int count = entropy.randomInt(this.count); count > 0 && !chooser.isEmpty(); count--) {
final Option<T> option = chooser.choose(entropy);
result = Stream.concat(result, option.element.elements(query));
if(unique) chooser.remove(option);
}
return result;
}
}