I was asked to Design a Circuit board in a recent 1 hour long interview. I couldn't complete the design during the call, but went ahead and completed it later (implementation down below.)
I'm looking for more suggestions on how it can be improved - especially the data models part. Thanks in advance!

Problem

Design a Circuit board system that can contain one or more switches in any specific arrangement as defined by User input (Assume inputs from command line, UI not required).

Rules:

  • A switch can be of type XOR, OR, AND, NOT.
  • A switch can have one or more inputs. Each input can be 0 or 1.
  • A switch will always result in one output which is 0 or 1.
  • It is possible to arrange switches in series or parallel. And there can be intermediate switches such that inputs of switch is coming from output to other switches.
  • As a system, the circuit should result in a single output.

Expectation:

  • Data Models to support such a system.
  • Algorithm to calculate the resultant output (0 or 1) of the Circuit board.

Solution

The Circuit board has been modeled as a graph, where switches are defined as nodes.
Parent connections are needed to compute the output of any node.


/**
 
BoardManager
    BoardSystem

BoardSystem - Holds the state
    Map<Integer, Node> nodes
    Map<Integer, List<Integer>> parents
    initialize(int[] nodes, int[][] edges, String[] types)
    int compute()
    addNode(id, type)
    addEdge(parentNodeId, childNodeId)


 Node
     id
     type
     BoardProcessor processor - Defines the calculation behavior - OR/AND/XOR.
			- My initial thinking is that each node can be subclassed into Or, And, .. for node level calculation. 
			- But its a reasonable assumption that users want to edit/change the compute behavior of node at run time. 
			- So Strategy pattern works best to compose this behavior into a Processor class.

 BoardProcessor
    int compute(List<Node> parents)

 <Concrete Processor classes>
    OnProcessor - always 1, for initial input switches
    OffProcessor - always 0, for initial input switches
    OrProcessor
    AndProcessor
    XorProcessor
    NotProcessor

 */

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


class BoardManager {
    public static void main(String[] args) {
        BoardSystem boardSystem = new BoardSystem();

        boardSystem.initialize(
                new int[]{1, 2, 3},
                new int[][]{{1,3}, {2,3}},
                new String[]{"OFF", "ON", "XOR"}
        );

        Preconditions.validateEquals(1, boardSystem.compute());

        boardSystem.initialize(
                new int[]{1, 2, 3, 4, 5},
                new int[][]{{1, 3}, {2, 3}, {1, 4}, {2, 4}, {3, 5}, {4, 5}},
                new String[]{"ON", "OFF", "XOR", "AND", "OR"}
        );
        Preconditions.validateEquals(1, boardSystem.compute());

    }

}

class BoardSystem {
    Map<Integer, Node> nodes;
    Map<Integer, List<Node>> parents;
    public BoardSystem () {
        nodes = new HashMap<>();
        parents = new HashMap<>();

    }
    void initialize(int[] nodes, int[][] edges, String[] types) {

        this.nodes.clear();
        this.parents.clear();

        assert nodes.length != 0;
        assert edges.length != 0;
        assert nodes.length == types.length;

        for(int i=0;i<nodes.length;i++){
            addNode(nodes[i], Enum.valueOf(BoardType.class, types[i]));
        }
        for (int[] edge : edges) {
            addEdge(edge[0], edge[1]);
        }

    }

    void addNode(int id, BoardType type){
        Node node = new Node(id, type);
        // validate node with id not exists
        nodes.put(id, node);
    }

    void addEdge(int parentId, int childId) {
        // validate not cycle
        parents.putIfAbsent(childId, new ArrayList<>());
        parents.get(childId).add(nodes.get(parentId));
    }

    List<Integer> sortedNodes;
    Map<Integer, Integer> resultMap;
    int compute() {
        sortedNodes = new ArrayList<>();
        resultMap = new HashMap<>();

        for (int nodeId : nodes.keySet()) {
            dfs(nodeId);
        }

        int last = sortedNodes.get(sortedNodes.size() - 1);
        return resultMap.get(last);
    }


    private void dfs(int nodeId) {
        if (resultMap.containsKey(nodeId)) {
            return;
        }
        Node node = nodes.get(nodeId);
        if (node.inputs == null && this.parents.containsKey(nodeId)) {
            node.setInputs(this.parents.get(nodeId));
        }
        if(node.inputs == null) return;

        for (Node parent : node.inputs) {
            dfs(parent.id);
        }
        int res = node.processor.compute(node.inputs);
        resultMap.put(nodeId, res);
        sortedNodes.add(nodeId);
    }
}

enum BoardType {
    OFF, ON, XOR, OR, AND, NOT
}

class Node {
    int id;
    List<Node> inputs;
    BoardType type;
    BoardProcessor processor;
    public Node(BoardType type) {
        this.type = type;
    }

    public void setInputs(List<Node> inputs) {
        this.inputs = inputs;
    }

    public Node(int id, BoardType type) {
        this.id = id;
        this.type = type;
        this.processor = BoardProcessor.getInstance(type);
    }

}

interface BoardProcessor {
    int compute(List<Node> parents);

    static BoardProcessor getInstance(BoardType type){
        switch (type) {

            case XOR:
                return new XorProcessor();
            case OR:
                return new OrProcessor();
            case AND:
                return new AndProcessor();
            case ON:
                return new OnProcessor();
            case OFF:
                return new OffProcessor();
            default:
                throw new UnsupportedOperationException();
        }
    }
}
class AndProcessor implements BoardProcessor {

    @Override
    public int compute(List<Node> inputs) {
        int res = 1;
        for (Node parent : inputs) {
            res &= parent.processor.compute(parent.inputs);
        }
        return res;
    }
}
class OrProcessor implements BoardProcessor {

    @Override
    public int compute(List<Node> inputs) {
        int res = 0;
        for (Node parent : inputs) {
            res |= parent.processor.compute(parent.inputs);
        }
        return res;
    }
}
class XorProcessor implements BoardProcessor {

    @Override
    public int compute(List<Node> inputs) {
        int res = 0;
        for (Node node: inputs) {
            res ^= node.processor.compute(node.inputs);
        }
        return res;
    }
}

class OnProcessor implements BoardProcessor {

    @Override
    public int compute(List<Node> inputs) {
        return 1;
    }
}
class OffProcessor implements BoardProcessor {

    @Override
    public int compute(List<Node> inputs) {
        return 0;
    }
}

class Preconditions {
    public static <T> boolean validateEquals(T expected, T result) {
        if (expected.toString().equals(result.toString())) {
            return true;
        }
        throw new IllegalArgumentException();
    }
}
Comments (3)