diff --git a/hierarchical-parentheses/hierarchical-parentheses.iml b/hierarchical-parentheses/hierarchical-parentheses.iml new file mode 100644 index 0000000..e802c7b --- /dev/null +++ b/hierarchical-parentheses/hierarchical-parentheses.iml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hierarchical-parentheses/pom.xml b/hierarchical-parentheses/pom.xml new file mode 100644 index 0000000..3ffa608 --- /dev/null +++ b/hierarchical-parentheses/pom.xml @@ -0,0 +1,63 @@ + + + + 4.0.0 + + com.demo + demo + 1.0-SNAPSHOT + + demo + + + UTF-8 + 1.8 + 1.8 + + + + + junit + junit + 4.11 + test + + + + + + + + maven-clean-plugin + 3.0.0 + + + + maven-resources-plugin + 3.0.2 + + + maven-compiler-plugin + 3.7.0 + + + maven-surefire-plugin + 2.20.1 + + + maven-jar-plugin + 3.0.2 + + + maven-install-plugin + 2.5.2 + + + maven-deploy-plugin + 2.8.2 + + + + + diff --git a/hierarchical-parentheses/src/main/java/com/demo/App.java b/hierarchical-parentheses/src/main/java/com/demo/App.java new file mode 100644 index 0000000..c1f8a86 --- /dev/null +++ b/hierarchical-parentheses/src/main/java/com/demo/App.java @@ -0,0 +1,8 @@ +package com.demo; + +public class App +{ + public static void main( String[] args ) { + + } +} diff --git a/hierarchical-parentheses/src/main/java/com/demo/Equation.java b/hierarchical-parentheses/src/main/java/com/demo/Equation.java new file mode 100644 index 0000000..26dd5b4 --- /dev/null +++ b/hierarchical-parentheses/src/main/java/com/demo/Equation.java @@ -0,0 +1,58 @@ +package com.demo; + +public class Equation { + private String input; + + /** + * Constructor to set an equation as a string. E.g. "{[(2 + 5) * (2 + 1)] * 2 + 1} * 3" + * @param input the equation. + */ + public Equation(String input) { + this.input = input; + } + + /** + * Checks if the hierarchy of the parenthesis is valid. + * Validity rules: + * - contains only `(), [], {}` braces + * - all open braces are also closed + * - hierarchy of braces is in the following order: `{[()]}` or `{{{[()]}}}` + * + * @return [boolean] if equation parenthesis are valid. + */ + public boolean isValid() { + Node root = new Node(true); + + try { + return input.chars() + .filter(c -> Node.OPEN_CLOSE_BRACES.containsKey((char)c) || Node.OPEN_CLOSE_BRACES.containsValue((char)c)) + .mapToObj(v -> new Node((char) v)) + .reduce(root, (currentNode, node) -> { + if (node.isOpenBrace()) { + if (currentNode.canContain(node)) { + node.setParent(currentNode); + currentNode.getChildren().add(node); + return node; + } else throw new BrokenEquationException(currentNode.getValue()+" can not contain subnode "+node.getValue()); + } else { // else is close brace + if (currentNode.isCloseableWith(node) && currentNode.isHierarchicallyComplete()) + return currentNode.getParent(); + else throw new BrokenEquationException(currentNode.getValue()+" is not closeable with "+node.getValue()+" or node does not have any hierarchical sub brace."); + } + }).isRoot(); + } catch (BrokenEquationException ex) { + return false; + } + + } + + public class BrokenEquationException extends RuntimeException { + public BrokenEquationException() { + super(); + } + public BrokenEquationException(String message) { + super(message); + } + } +} + diff --git a/hierarchical-parentheses/src/main/java/com/demo/Node.java b/hierarchical-parentheses/src/main/java/com/demo/Node.java new file mode 100644 index 0000000..449adf0 --- /dev/null +++ b/hierarchical-parentheses/src/main/java/com/demo/Node.java @@ -0,0 +1,203 @@ +package com.demo; + +import java.util.*; + +public class Node { + /** + * Contains a map of open and corresponding closing braces. + */ + protected static final Map OPEN_CLOSE_BRACES = new HashMap<>(); + + /** + * Each brace should have a weight. + * Starting from most inner brace until most exterior brace they should be weighted in order with a step of 1. + */ + private static final Map WEIGHT = new HashMap<>(); + + /** + * The minimum weight of the braces. + */ + private static final int MIN_WEIGHT; + + /** + * The maximum weight of the braces. + */ + private static final int MAX_WEIGHT; + + /* + * Braces configuration. + */ + static { + OPEN_CLOSE_BRACES.put('(', ')'); + OPEN_CLOSE_BRACES.put('[', ']'); + OPEN_CLOSE_BRACES.put('{', '}'); +// OPEN_CLOSE_BRACES.put('<', '>'); + + WEIGHT.put('(', 1); + WEIGHT.put('[', 2); + WEIGHT.put('{', 3); +// WEIGHT.put('<', 4); + + MIN_WEIGHT = WEIGHT.values().stream().min(Integer::compareTo).orElseThrow(IllegalArgumentException::new); + MAX_WEIGHT = WEIGHT.values().stream().max(Integer::compareTo).orElseThrow(IllegalArgumentException::new); + } + + /** + * It is the starting node that does not contain any value. + */ + private boolean isRoot; + + /** + * The opening or closing brace of current node. + */ + private Character value; + private Node parent; + private List children; +// private boolean isClosed; // YAGNI + + public Node() { + isRoot = false; + children = new ArrayList<>(); + } + + public Node(Character value) { + this(); + this.value = value; + } + + public Node(boolean isRoot) { + this(); + this.isRoot = isRoot; + } + + public Node(Character value, Node parent) { + this(); + this.value = value; + this.parent = parent; + } + + /** + * Checks if current node contains a value of an opening brace. + * + * @return [boolean] + */ + public boolean isOpenBrace() { + return OPEN_CLOSE_BRACES.containsKey(value); + } + + /** + * Checks if current node contains a value of an closing brace. + * + * @return [boolean] + */ + public boolean isClosedBrace() { + return OPEN_CLOSE_BRACES.containsValue(value); + } + + /** + * Checks if node can hierarchically be a subnode of current node. + * + * @param node the new node + * @return [boolean] + */ + public boolean canContain(Node node) { + return isRoot() || (getWeight() > node.getWeight()) || (getWeight()==MAX_WEIGHT && node.getWeight()==MAX_WEIGHT); + } + + /** + * If current node is root node. + * + * @return [boolean] + */ + public boolean isRoot() { + return isRoot; + } + + /** + * Checks if current node can be closed with specified node. + * + * @param closingNode the node to close current node with + * @return [boolean] + */ + public boolean isCloseableWith(Node closingNode) { + return OPEN_CLOSE_BRACES.get(value).equals(closingNode.getValue()); + } + + /** + * Checks if current node is hierarchically complete. If it's the lowest weighted or has any direct hierarchical subbraces. + * + * @return [boolean] + */ + public boolean isHierarchicallyComplete() { + return (getWeight()==MIN_WEIGHT) || hasAnyHierarchicalSubBrace(); + } + + /** + * Checks if current node has any direct hierarchical subbraces. + * + * @return [boolean] + */ + private boolean hasAnyHierarchicalSubBrace() { + return getChildren().stream().anyMatch(childNode -> (childNode.getWeight()+1==getWeight()) || (childNode.getWeight()==MAX_WEIGHT && getWeight()==MAX_WEIGHT)); + } + + /** + * Returns the weight of the current node from the weight Set. + * + * @return [int] + */ + public int getWeight() { + return WEIGHT.get(value); + } + + public Character getValue() { + return value; + } + + public void setValue(Character value) { + this.value = value; + } + + public Node getParent() { + return parent; + } + + public void setParent(Node parent) { + this.parent = parent; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Node node = (Node) o; + return isRoot == node.isRoot && + Objects.equals(value, node.value) && + Objects.equals(parent, node.parent) && + Objects.equals(children, node.children); + } + + @Override + public int hashCode() { + return Objects.hash(isRoot, value, parent, children); + } + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer("Node{"); + sb.append("isRoot=").append(isRoot); + sb.append(", value=").append(value); + sb.append(", parent=").append(parent); + sb.append(", children=").append(children); + sb.append('}'); + return sb.toString(); + } +} diff --git a/hierarchical-parentheses/src/test/java/com/demo/AppTest.java b/hierarchical-parentheses/src/test/java/com/demo/AppTest.java new file mode 100644 index 0000000..06bb42c --- /dev/null +++ b/hierarchical-parentheses/src/test/java/com/demo/AppTest.java @@ -0,0 +1,116 @@ +package com.demo; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class AppTest +{ + @Test + public void shouldCheckEmptyString(){ + assertTrue("Should validate empty string equation.", new Equation("").isValid()); + } + + @Test + public void shouldCheckEquationWithNoParenthesis() { + assertTrue("Should validate equation with no parenthesis.", new Equation("5 * 5").isValid()); + } + + @Test + public void shouldCheckEquationWithOneOpenParenthesis() { + assertFalse("Should invalidate equation with one open parenthesis.", new Equation("2 * (2 + 1").isValid()); + } + + @Test + public void shouldCheckEquationWithOneOpenAndOneClosedParenthesis() { + assertTrue("Should validate equation with one open and one close parenthesis.", new Equation("2 * (2 + 1)").isValid()); + } + + @Test + public void shouldCheckEquationWithTwoOpenAndOneClosedParenthesis() { + assertFalse("Should invalidate equation with two open and one close parenthesis.", new Equation("(2 * (2 + 1)").isValid()); + } + + @Test + public void shouldCheckEquationWithMultipleInOrderOpenAndClosedRoundParenthesis() { + assertTrue("Should validate equation with multiple in order open and close round parenthesis.", + new Equation("(2 + 5) * (2 + 1)").isValid()); + } + + @Test + public void shouldCheckEquationWithMultipleInOrderOpenAndClosedRoundAndSquareParenthesis() { + assertTrue("Should validate equation with multiple in order open and close round and square parenthesis.", + new Equation("[(2 + 5) * (2 + 1)] * 2").isValid()); + } + + @Test + public void shouldCheckEquationWithOneOpenSquareAndMultipleInOrderOpenAndClosedRoundParenthesis() { + assertFalse("Should validate equation with one square brace and multiple in order open and close round parenthesis.", + new Equation("[(2 + 5) * (2 + 1) * 2").isValid()); + } + + @Test + public void shouldCheckEquationWithMultipleInOrderOpenAndClosedRoundAndSquareAndCurlyParenthesis() { + assertTrue("Should validate equation with multiple in order open and close round, square and curly parenthesis.", + new Equation("{[(2 + 5) * (2 + 1)] * 2 + 1} * 3").isValid()); + } + + @Test + public void shouldCheckEquationWithOneOpenRoundAndOneClosedSquareParenthesis() { + assertFalse("Should invalidate equation with one open round and one closed square parenthesis.", + new Equation("(2 + 5] * 2").isValid()); + } + + @Test + public void shouldCheckEquationWithOneOpenCurlyAndMultipleInOrderOpenAndClosedRoundAndSquareParenthesis() { + assertFalse("Should validate equation with one open curly and multiple in order open and close round, square parenthesis.", + new Equation("{[(2 + 5) * (2 + 1)] * 2 + 1 * 3").isValid()); + } + + @Test + public void shouldCheckEquationWithMultipleHierarchicalOpenAndClosedRoundAndSquareAndCurlyParenthesisWithMultipleInnerParenthesis() { + assertTrue("Should validate equation with multiple hierarchical open and close round, square and curly parenthesis where there are multiple different inner parenthesis groups (1).", + new Equation("{(4 + 1) * { 5 * [( 5 ^ 2 - 4 ^ 2 ) * 2 ]}} / 2").isValid()); + assertTrue("Should validate equation with multiple hierarchical open and close round, square and curly parenthesis where there are multiple different inner parenthesis groups (2).", + new Equation("{[(2 + 5) * 2] + 1} * (2 + 1) * {[(2 + 5) * 2] + 1} * 2").isValid()); + assertTrue("Should validate equation with multiple hierarchical open and close round, square and curly parenthesis and with multiple root curly braces.", + new Equation("{[(2 + 5) * 2] + 1} * (2 + 1) * {[(2 + 5) * 2] + 1} / {[(2 + 5) * 2] + 1} * (2 + 1) * {[(2 + 5) * 2] + 1} * 2").isValid()); + assertTrue("Should validate equation with multiple hierarchical open and close round, square and curly parenthesis and with multiple root curly braces.", + new Equation("{{[(2 + 5) * 2] + 1} * (2 + 1) * {[(2 + 5) * 2] + 1} / {[(2 + 5) * 2] + 1} * (2 + 1) * {[(2 + 5) * 2] + 1} * 2} / 5").isValid()); + } + + @Test + public void shouldCheckEquationWithMultipleHierarchicalOpenAndClosedSquareAndCurlyParenthesisWithoutInnerRoundParenthesis() { + assertFalse("Should invalidate equation with multiple hierarchical open and close square and curly parenthesis without inner round parenthesis.", + new Equation("{[2 + 5] * 2} * 3").isValid()); + } + + @Test + public void shouldCheckEquationWithWrongHierarchicalOrderOfOpenAndClosedSquareAndCurlyAndRoundParenthesis() { + assertFalse("Should invalidate equation with wrong hierarchical order of open and closed square, curly and round parenthesis.", + new Equation("{([2 + 5] / 2) * 2} * 3").isValid()); + } + + @Test + public void shouldCheckEquationWithMultipleNotHierarchicalOpenAndClosedRoundAndSquareAndCurlyParenthesis() { + assertFalse("Should invalidate equation with multiple NOT hierarchical open and close round, square and curly parenthesis (1).", + new Equation("[({2 + 5} * (2 + 1)) * 2 + 1] * 3").isValid()); + assertFalse("Should invalidate equation with multiple NOT hierarchical open and close round, square and curly parenthesis (2).", + new Equation("{2 * (2 + 1)} * 3").isValid()); + assertFalse("Should invalidate equation with multiple NOT hierarchical open and close round, square and curly parenthesis (3).", + new Equation("{[2 + 5] * 2} * 3 / (3 - 1)").isValid()); + assertFalse("Should invalidate equation with multiple NOT hierarchical open and close round, square and curly parenthesis (4).", + new Equation("[{2 + 5} * (2 + 1) * {2 + 1}] * 2").isValid()); + assertFalse("Should invalidate equation with multiple NOT hierarchical open and close round, square and curly parenthesis (5).", + new Equation("{{[(2 + 5) * 2] + 1} * (2 + 1) * {[(2 + 5) * 2] + 1} / {[(2 + 5) * 2] + 1} * (2 + 1) * {[2 + 5 * 2] + 1} * 2} / 5").isValid()); + } + + +// @Test +// public void shouldCheckEquationWithExtraBraces() { +// assertTrue("Should validate equation with extra braces.", +// new Equation("<{[(2 + 5) * (2 + 1)] * 2} + 1> * 3").isValid()); +// } + +}