-
Notifications
You must be signed in to change notification settings - Fork 154
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #98 from jeantimex/evaluate-division
feat(graph): Added evaluate division.
- Loading branch information
Showing
3 changed files
with
145 additions
and
0 deletions.
There are no files selected for viewing
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
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
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); | ||
}); | ||
}); | ||
}); |
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
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; |