Last active
August 5, 2021 01:58
-
-
Save sbvalijon/4f79ce6f0baa0d94142cd2664c363202 to your computer and use it in GitHub Desktop.
Checks if all packages can be installed and prints the installation order
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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