Skip to content

Commit

Permalink
Merge pull request #98 from jeantimex/evaluate-division
Browse files Browse the repository at this point in the history
feat(graph): Added evaluate division.
  • Loading branch information
jeantimex authored Nov 16, 2017
2 parents c96b4d8 + 4124017 commit f617164
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ A collection of JavaScript problems and solutions for studying algorithms.
- [Graph Valid Tree](src/graph/graph-valid-tree.js)
- [Number of Connected Components in an Undirected Graph](src/graph/number-of-connected-components-in-an-undirected-graph.js)
- [Reconstruct Itinerary](src/graph/reconstruct-itinerary.js)
- [Evaluate Division](src/graph/evaluate-division.js)

### Linked List

Expand Down
26 changes: 26 additions & 0 deletions src/graph/__tests__/evaluate-division-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { assert } from 'chai';
import { serializeUndirectedGraph, deserializeUndirectedGraph } from 'utils/graph-util';
import calcEquation from '../evaluate-division';

describe('Evaluate Division', () => {
const testCases = [
[
[['a', 'b'], ['b', 'c']],
[2.0, 3.0],
[['a', 'c'], ['b', 'a'], ['a', 'e'], ['a', 'a'], ['x', 'x']],
[6.0, 0.5, -1.0, 1.0, -1.0],
],
[[['a', 'b'], ['c', 'b']], [2.0, 4.0], [['a', 'c'], ['b', 'a']], [0.5, 0.5]],
];

testCases.forEach((testCase, index) => {
it(`should evaluate the division ${index}`, () => {
const equations = testCase[0];
const values = testCase[1];
const queries = testCase[2];
const expected = testCase[3];
const actual = calcEquation(equations, values, queries);
assert.deepEqual(actual, expected);
});
});
});
118 changes: 118 additions & 0 deletions src/graph/evaluate-division.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/**
* Equations are given in the format A / B = k, where A and B are variables represented as strings,
* and k is a real number (floating point number). Given some queries, return the answers.
*
* If the answer does not exist, return -1.0.
*
* Example:
* Given a / b = 2.0, b / c = 3.0.
* queries are: a / c = ?, b / a = ?, a / e = ?, a / a = ?, x / x = ? .
* return [6.0, 0.5, -1.0, 1.0, -1.0 ].
*
* The input is: vector<pair<string, string>> equations, vector<double>& values,
* vector<pair<string, string>> queries , where equations.size() == values.size(),
* and the values are positive. This represents the equations. Return vector<double>.
*
* According to the example above:
*
* equations = [ ["a", "b"], ["b", "c"] ],
* values = [2.0, 3.0],
* queries = [ ["a", "c"], ["b", "a"], ["a", "e"], ["a", "a"], ["x", "x"] ].
*
* The input is always valid. You may assume that evaluating the queries will result in no division
* by zero and there is no contradiction.
*/

/**
* @param {string[][]} equations
* @param {number[]} values
* @param {string[][]} queries
* @return {number[]}
*/
const calcEquation = (equations, values, queries) => {
const result = [];

// Step 1. Build the undirected graph with adjacency list
const adjList = buildGraph(equations, values);

// Step 2. For each query, try to find a path in the graph
// that can link the nodes in the query
for (let i = 0; i < queries.length; i++) {
const [from, to] = queries[i];
const value = dfs(adjList, from, to, 1, new Set());

// If value is null, that means there's no such path
result.push(value ? value : -1.0);

if (value) {
// Update the graph to avoid duplicate computation
adjList.get(from).set(to, value);
adjList.get(to).set(from, 1 / value);
}
}

return result;
};

/**
* @param {Map<string, Map<string, number>>} adjList
* @param {string} node
* @param {string} to
* @param {number} product
* @param {Set} visited
*/
const dfs = (adjList, node, to, product, visited) => {
if (!adjList.has(node)) {
return null; // Dead end
}

visited.add(node); // Mark the current node as visited

const neighbors = [...adjList.get(node).keys()];

for (let i = 0; i < neighbors.length; i++) {
const v = neighbors[i];
const current = product * adjList.get(node).get(v);

if (v === to) {
// Found the path, return the product
return current;
}

if (!visited.has(v)) {
// Continue to search for the path
const value = dfs(adjList, v, to, current, visited);

if (value) {
return value;
}
}
}

return null;
};

const buildGraph = (equations, values) => {
const adjList = new Map();

for (let i = 0; i < equations.length; i++) {
const [from, to] = equations[i];
const value = values[i];

if (!adjList.has(from)) {
adjList.set(from, new Map());
}

if (!adjList.has(to)) {
adjList.set(to, new Map());
}

// Build the undirected graph
adjList.get(from).set(to, value);
adjList.get(to).set(from, 1 / value);
}

return adjList;
};

export default calcEquation;

0 comments on commit f617164

Please sign in to comment.