All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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