Skip to content

Instantly share code, notes, and snippets.

@sbvalijon
Last active August 5, 2021 01:58
Show Gist options
  • Save sbvalijon/4f79ce6f0baa0d94142cd2664c363202 to your computer and use it in GitHub Desktop.
Save sbvalijon/4f79ce6f0baa0d94142cd2664c363202 to your computer and use it in GitHub Desktop.
Checks if all packages can be installed and prints the installation order
package uz.valijon;
import static uz.valijon.PackageState.CHECKED_CANNOT_BE_INSTALLED;
import static uz.valijon.PackageState.CHECKED_CAN_BE_INSTALLED;
import static uz.valijon.PackageState.UNCHECKED;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public class Solution {
public static void main(String[] args) {
//test case 1
Package p0 = new Package("p0");
Package p1 = new Package("p1");
Package p2 = new Package("p2");
Package p3 = new Package("p3");
Package p4 = new Package("p4");
p2.init();
p3.init(p2);
p1.init(p3);
p0.init(p1);
p4.init(p1);
System.out.println("Expected true. Actual = " + canAllPackagesBeInstalled(p0, p1, p2, p3, p4));
//print the installation order
printInstallationOrder(p0, p1, p2, p3, p4);
//test case 2
//canBeInstalled() modifies state so we need to reinitialize packages to test a different scenario
p0 = new Package("p0");
p1 = new Package("p1");
p2 = new Package("p2");
p3 = new Package("p3");
p4 = new Package("p4");
p2.init(p1);
p3.init(p2);
p1.init(p3);
p0.init(p1);
p4.init(p1);
System.out.println("Expected false - has cyclic dependency. Actual = " + canAllPackagesBeInstalled(p0, p1, p2, p3, p4));
//uncomment the line below to see the exception when cyclic dependency
// printInstallationOrder(p0, p1, p2, p3, p4);
}
static boolean canAllPackagesBeInstalled(Package... packages) {
for (Package p : packages) {
if (!p.canBeInstalled()) {
return false;
}
}
return true;
}
static void printInstallationOrder(Package... allPackages) {
List<Package> allPackagesList = new ArrayList<>();
List<Package> independentPackages = new ArrayList<>();
for (Package p : allPackages) {
allPackagesList.add(p);
if (!p.canBeInstalled()) {
throw new IllegalStateException("Cannot be installed");
}
if (!p.hasDepedency()) {
independentPackages.add(p);
}
}
independentPackages.forEach(p -> {
System.out.println("installing... " + p);
allPackagesList.remove(p);
for (Package d : p.getDependentsAtAllLevels()) {
boolean isAlreadyInstalled = !allPackagesList.contains(d);
if (isAlreadyInstalled) {
continue;
}
System.out.println("installing... " + d);
allPackagesList.remove(d);
}
});
}
}
class Package {
private final String name;
private List<Package> dependencies;
private PackageState state;
private final List<Package> flatDependents;
public Package(String name) {
this.name = name;
this.state = UNCHECKED;
this.flatDependents = new ArrayList<>();
}
public void init(Package... dependencies) {
this.dependencies = new ArrayList<>();
for (Package p : dependencies) {
this.dependencies.add(p);
}
}
//It's not a good idea to modify state in such method, but in our case it helps to reduce the complexity
public boolean canBeInstalled() {
if (state == CHECKED_CAN_BE_INSTALLED) {
return true;
}
for (Package p : dependencies) {
try {
p.addDependent(this);
} catch (CyclicDependencyException e) {
this.state = CHECKED_CANNOT_BE_INSTALLED;
return false;
}
if (!p.canBeInstalled()) {
this.state = CHECKED_CANNOT_BE_INSTALLED;
return false;
}
}
this.state = CHECKED_CAN_BE_INSTALLED;
return true;
}
public boolean hasDepedency() {
return !dependencies.isEmpty();
}
public List<Package> getDependentsAtAllLevels() {
return this.flatDependents;
}
private void addDependent(Package dependent) {
if (this.flatDependents.contains(dependent)) {
dependent.state = CHECKED_CANNOT_BE_INSTALLED;
throw new CyclicDependencyException(String.format("%s has cyclic dependency", dependent.name));
} else {
this.flatDependents.add(dependent);
}
//add all dependents of passed package
for (Package d : dependent.flatDependents) {
if (this.flatDependents.contains(d)) {
dependent.state = CHECKED_CANNOT_BE_INSTALLED;
throw new CyclicDependencyException(String.format("%s has cyclic dependency", dependent.name));
} else {
this.flatDependents.add(d);
}
}
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final Package aPackage = (Package) o;
return name.equals(aPackage.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
@Override
public String toString() {
return "Package{" +
"name='" + name + '\'' +
", dependencies=" + dependencies.stream().map(p -> p.name).collect(Collectors.toList()) +
", state=" + state +
", flatDependents=" + flatDependents.stream().map(p -> p.name).collect(Collectors.toList()) +
'}';
}
}
enum PackageState {
UNCHECKED,
CHECKED_CAN_BE_INSTALLED,
CHECKED_CANNOT_BE_INSTALLED
}
class CyclicDependencyException extends RuntimeException {
public CyclicDependencyException(String message) {
super(message);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment