
it.unibz.inf.ontop.iq.request.impl.FunctionalDependenciesImpl Maven / Gradle / Ivy
package it.unibz.inf.ontop.iq.request.impl;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import it.unibz.inf.ontop.iq.request.FunctionalDependencies;
import it.unibz.inf.ontop.model.term.Variable;
import it.unibz.inf.ontop.substitution.InjectiveSubstitution;
import it.unibz.inf.ontop.substitution.SubstitutionFactory;
import it.unibz.inf.ontop.utils.ImmutableCollectors;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.*;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class FunctionalDependenciesImpl implements FunctionalDependencies {
private final ImmutableSet dependencies;
public FunctionalDependenciesImpl(ImmutableSet, ImmutableSet>> dependencies) {
this.dependencies = dependencies.stream()
.map(entry -> new FunctionalDependency(entry.getKey(), entry.getValue()))
.collect(ImmutableCollectors.toSet());
}
/*
* The performance of this method is critical.
* Currently, it has a complexity around O(n^2*m) where `n` is the number of FDs and `m` the number of variables in it.
*/
private static boolean inferTransitiveDependencies(ImmutableSet, Set>> mutableDependencies) {
boolean changed = false;
for(var entry : mutableDependencies) {
for(var entry2 : mutableDependencies) {
if(Sets.difference(entry.getKey(), entry2.getKey()).isEmpty()
|| Sets.difference(Sets.difference(entry.getValue(), entry2.getKey()), entry2.getValue()).isEmpty()
|| !Sets.union(entry2.getValue(), entry2.getKey()).containsAll(entry.getKey()))
continue;
entry2.getValue().addAll(Sets.difference(entry.getValue(), entry2.getKey()));
changed = true;
}
}
return changed;
}
@Override
public Stream, ImmutableSet>> stream() {
return dependencies.stream()
.map(fd -> Maps.immutableEntry(fd.determinants, fd.dependents));
}
@Override
public FunctionalDependencies rename(InjectiveSubstitution renamingSubstitution, SubstitutionFactory substitutionFactory) {
return new FunctionalDependenciesImpl(dependencies.stream()
.map(fd -> fd.rename(renamingSubstitution, substitutionFactory))
.map(fd -> Maps.immutableEntry(fd.determinants, fd.dependents))
.collect(ImmutableCollectors.toSet())
).complete();
}
@Override
public boolean isEmpty() {
return dependencies.isEmpty();
}
@Override
public FunctionalDependencies concat(FunctionalDependencies other) {
return new FunctionalDependenciesImpl(
Stream.concat(stream(), other.stream())
.collect(ImmutableCollectors.toSet())
).complete();
}
private ImmutableSet> mergeDeterminants(ImmutableSet> left, ImmutableSet> right) {
return left.stream()
.flatMap(l -> right.stream()
.map(r -> Sets.union(l, r).immutableCopy()))
.collect(ImmutableCollectors.toSet());
}
@Override
public FunctionalDependencies merge(FunctionalDependencies other) {
var allDependents = Streams.concat(
dependencies.stream()
.flatMap(fd -> fd.dependents.stream())
).collect(ImmutableCollectors.toSet());
return allDependents.stream()
.flatMap(v -> mergeDeterminants(getDeterminantsOf(v), other.getDeterminantsOf(v)).stream()
.map(d -> Maps.immutableEntry(d, ImmutableSet.of(v))))
.collect(FunctionalDependencies.toFunctionalDependencies());
}
@Override
public boolean contains(ImmutableSet determinants, ImmutableSet dependents) {
return dependencies.stream()
.anyMatch(dp -> determinants.containsAll(dp.determinants) && dp.dependents.containsAll(dependents));
}
@Override
public String toString() {
return String.format("[%s]", String.join("; ", dependencies.stream()
.map(FunctionalDependency::toString)
.collect(ImmutableCollectors.toList())));
}
@Override
public ImmutableSet> getDeterminantsOf(Variable variable) {
return dependencies.stream()
.filter(d -> d.dependents.contains(variable))
.map(FunctionalDependency::getDeterminants)
.collect(ImmutableCollectors.toSet());
}
/**
* Completes the list of functional dependencies by applying the following actions:
* - Infer transitive dependencies
* - Merge dependencies with related determinants
* - Remove duplicate dependencies
* - Remove empty dependencies
*/
protected FunctionalDependencies complete() {
var dependencyPairs = stream().collect(ImmutableCollectors.toSet());
var withTransitive = dependencyPairs.stream()
.map(entry -> Maps., Set>immutableEntry(entry.getKey(), new HashSet<>(entry.getValue())))
.collect(ImmutableCollectors.toSet());
while(inferTransitiveDependencies(withTransitive));
var collectedDependencies = withTransitive.stream()
.collect(Collectors.groupingBy(Map.Entry::getKey))
.entrySet().stream()
.map(e -> new FunctionalDependency(e.getKey(), ImmutableSet.copyOf(e.getValue().stream()
.reduce(
Set.of(),
(set, entry) -> Sets.union(set, entry.getValue()),
(set1, set2) -> Sets.union(set1, set2)
)
)))
.collect(ImmutableCollectors.toSet());
var dependenciesToRemove = collectedDependencies.stream()
.collect(ImmutableCollectors.toMap(
fd -> fd,
fd -> collectedDependencies.stream()
.filter(dependencies2 -> !dependencies2.equals(fd))
.filter(dependency2 -> fd.determinants.containsAll(dependency2.determinants))
.flatMap(dependency2 -> Sets.intersection(fd.dependents, dependency2.dependents).stream())
.collect(ImmutableCollectors.toSet())
));
var packedDependencies = Streams.concat(
collectedDependencies.stream()
.filter(fd -> dependenciesToRemove.get(fd).isEmpty()),
dependenciesToRemove.entrySet().stream()
.map(entry -> entry.getKey().removeDependents(entry.getValue()))
.filter(fd -> !fd.dependents.isEmpty())
)
.filter(fd -> !fd.dependents.isEmpty())
.collect(ImmutableCollectors.toSet());
return new FunctionalDependenciesImpl(packedDependencies.stream()
.map(fd -> Maps.immutableEntry(fd.determinants, fd.dependents))
.collect(ImmutableCollectors.toSet()));
}
public static Collector, ImmutableSet>, ?, FunctionalDependencies> getCollector() {
return new FunctionalDependenciesCollector();
}
@Override
public boolean equals(Object obj) {
if(!(obj instanceof FunctionalDependenciesImpl))
return false;
var fd = (FunctionalDependenciesImpl) obj;
return fd.dependencies.equals(dependencies);
}
@Override
public int hashCode() {
return dependencies.stream()
.reduce(0, (sum, n) -> sum ^ n.hashCode(), Integer::sum);
}
private static class FunctionalDependency {
private final ImmutableSet determinants;
private final ImmutableSet dependents;
public FunctionalDependency(ImmutableSet determinants, ImmutableSet dependents) {
this.determinants = determinants;
this.dependents = dependents;
}
public ImmutableSet getDeterminants() {
return determinants;
}
public ImmutableSet getDependents() {
return dependents;
}
@Override
public int hashCode() {
return determinants.hashCode() ^ dependents.hashCode();
}
@Override
public boolean equals(Object obj) {
if(!(obj instanceof FunctionalDependency))
return false;
FunctionalDependency other = (FunctionalDependency) obj;
return dependents.equals(other.dependents) && determinants.equals(other.determinants);
}
private FunctionalDependency rename(InjectiveSubstitution renamingSubstitution, SubstitutionFactory substitutionFactory) {
return new FunctionalDependency(
determinants.stream()
.map(vars -> substitutionFactory.apply(renamingSubstitution, vars))
.collect(ImmutableCollectors.toSet()),
dependents.stream()
.map(vars -> substitutionFactory.apply(renamingSubstitution, vars))
.collect(ImmutableCollectors.toSet())
);
}
public FunctionalDependency removeDependents(ImmutableSet dependents) {
return new FunctionalDependency(determinants, Sets.difference(this.dependents, dependents).immutableCopy());
}
@Override
public String toString() {
return String.format("(%s) --> (%s)",
String.join(", ", determinants.stream()
.map(Variable::toString)
.collect(Collectors.toList())),
String.join(", ", dependents.stream()
.map(Variable::toString)
.collect(Collectors.toList()))
);
}
}
protected static class FunctionalDependenciesCollector implements Collector, ImmutableSet>, ImmutableSet.Builder, ImmutableSet>>, FunctionalDependencies> {
@Override
public Supplier, ImmutableSet>>> supplier() {
return ImmutableSet::builder;
}
@Override
public BiConsumer, ImmutableSet>>, Map.Entry, ImmutableSet>> accumulator() {
return (builder, fd) -> builder.add(fd);
}
@Override
public BinaryOperator, ImmutableSet>>> combiner() {
return (builder1, builder2) -> {
builder1.addAll(builder2.build());
return builder1;
};
}
@Override
public Function, ImmutableSet>>, FunctionalDependencies> finisher() {
return builder -> new FunctionalDependenciesImpl(builder.build()).complete();
}
@Override
public Set characteristics() {
return Set.of(Characteristics.UNORDERED);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy