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!
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).
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();
}
}