com.happy3w.math.combination.DimCombinationMaker Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of math Show documentation
Show all versions of math Show documentation
Some kits for common use
package com.happy3w.math.combination;
import com.happy3w.java.ext.ListUtils;
import com.happy3w.java.ext.Pair;
import lombok.Getter;
import lombok.Setter;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
/**
* Used to combine multi dimension, generate all permutation and combination
*/
@Getter
@Setter
public class DimCombinationMaker {
private List>> dimensions = new ArrayList<>();
private boolean withNullDimension = false;
private boolean withOverRelation = false;
private final Map weightMaskMap = new HashMap<>();
private void initWeightMask() {
int weightMask = 1;
for (Pair> dimension : dimensions) {
for (V value : dimension.getValue()) {
String key = genWeightMaskKey(dimension.getKey(), value);
weightMaskMap.put(key, weightMask);
weightMask <<= 1;
}
}
}
private String genWeightMaskKey(K dimensionName, V dimensionValue) {
return MessageFormat.format("{0}-{1}", dimensionName, dimensionValue);
}
/**
* Generate combinations
* @return Stream of permutation, the score and mask is not initialized when withOverRelation=false
*/
public Stream> generateDetail() {
Stream> detailStream = generateNormal()
.map(r -> new DimCombineDetail<>(r));
if (withOverRelation) {
return initOverRelation(detailStream);
}
return detailStream;
}
/**
* Generate combinations
* @return Stream of permutation, each result is a list of dimension name and value.
*/
public Stream>> generateNormal() {
List>> dimensions = mayBeNullDimensions(this.dimensions, withNullDimension);
return StreamSupport.stream(CombineSpliterator.of(dimensions), false);
}
/**
* Generate combinations
* @return The result is only the value list, such as [['a1', 'b1'], ['a1', 'b2']...]
*/
public Stream> generateSimple() {
return generateNormal().map(r -> ListUtils.map(r, Pair::getValue));
}
private List>> mayBeNullDimensions(List>> dimensions, boolean withNullDimension) {
if (withNullDimension) {
return dimensions.stream().map(dimensionItem -> {
List newDimValues = new ArrayList<>(dimensionItem.getValue());
newDimValues.add(null);
return new Pair<>(dimensionItem.getKey(), newDimValues);
}).collect(Collectors.toList());
}
return dimensions;
}
private Stream> initOverRelation(Stream> detailStream) {
List> detailList = detailStream.peek(this::initScoreAndMask)
.collect(Collectors.toList());
for (DimCombineDetail curDetail : detailList) {
for (DimCombineDetail overedDetail : detailList) {
if (curDetail.isOver(overedDetail)) {
curDetail.addOveredCombine(overedDetail);
}
}
}
return detailList.stream();
}
public void initScoreAndMask(DimCombineDetail dimCombineDetail) {
int mask = 0;
int score = 0;
for (Pair dimensionValue : dimCombineDetail.getNormalResult()) {
if (dimensionValue.getValue() == null) {
continue;
}
String weightMaskKey = genWeightMaskKey(dimensionValue.getKey(), dimensionValue.getValue());
mask |= weightMaskMap.get(weightMaskKey);
score++;
}
dimCombineDetail.setScore(score);
dimCombineDetail.setMask(mask);
}
private static class CombineSpliterator extends Spliterators.AbstractSpliterator>> {
private List>> dimensions;
private int currentIndex = 0;
protected CombineSpliterator(List>> dimensions, long est) {
super(est, Spliterator.SIZED);
this.dimensions = dimensions;
}
@Override
public boolean tryAdvance(Consumer super List>> action) {
if (currentIndex >= this.estimateSize()) {
return false;
}
List> currentDimension = createDimension(currentIndex);
action.accept(currentDimension);
currentIndex++;
return true;
}
private List> createDimension(int currentIndex) {
List> dimensionValueList = new ArrayList<>();
int dimensionValue = currentIndex;
for (Pair> dimDef : dimensions) {
int index = dimensionValue % dimDef.getValue().size();
Pair newItem = new Pair<>(dimDef.getKey(), dimDef.getValue().get(index));
dimensionValueList.add(newItem);
dimensionValue /= dimDef.getValue().size();
}
return dimensionValueList;
}
public static CombineSpliterator of(List>> dimensions) {
int maxSize = calculateMaxSize(dimensions);
return new CombineSpliterator<>(dimensions, maxSize);
}
private static int calculateMaxSize(List>> dimensions) {
int size = 1;
for (Pair> dimension : dimensions) {
size *= dimension.getValue().size();
}
return size;
}
}
public static CombinationGeneratorBuilder builder() {
return new CombinationGeneratorBuilder();
}
public static class CombinationGeneratorBuilder {
private DimCombinationMaker generator = new DimCombinationMaker<>();
/**
* Add a dimension
* @param dimensionName Dimension name, Must not be null
* @param values dimension values
* @return This builder
*/
public CombinationGeneratorBuilder dimension(K dimensionName, V... values) {
if (dimensionName == null) {
throw new UnsupportedOperationException("dimensionName can not be null.");
}
generator.dimensions.add(Pair.ofList(dimensionName, values));
return this;
}
/**
* Add a dimension
* @param dimensionName Dimension name, Must not be null
* @param values dimension values
* @return This builder
*/
public CombinationGeneratorBuilder dimensionList(K dimensionName, Collection values) {
if (dimensionName == null) {
throw new UnsupportedOperationException("dimensionName can not be null.");
}
generator.dimensions.add(new Pair<>(dimensionName, new ArrayList<>(values)));
return this;
}
/**
* Add some dimensions
* @param dimensions The key is dimension name. The value is the dimension value range. It accept null as legal dimension value.There should not contains same value in value range, but this method does not check it.
* @return This builder
*/
public CombinationGeneratorBuilder dimensions(List>> dimensions) {
generator.dimensions.addAll(dimensions);
return this;
}
/**
* Config generator whether auto add null to dimension when generate Combine Result.
* @param withNull true means add null to result
* @return This builder
*/
public CombinationGeneratorBuilder withNullDimension(boolean withNull) {
generator.withNullDimension = withNull;
return this;
}
/**
* Config generator whether init CombineDetail.overCombines
* @param withOverRelation true means init CombineDetail.overCombines
* @return This builder
*/
public CombinationGeneratorBuilder withOverRelation(boolean withOverRelation) {
generator.withOverRelation = withOverRelation;
return this;
}
public DimCombinationMaker build() {
generator.initWeightMask();
return generator;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy