From fd73f148fb84fdc74b69118f5c737bd1397d1a23 Mon Sep 17 00:00:00 2001 From: mathusanm6 Date: Sun, 21 Sep 2025 19:05:57 +0200 Subject: [PATCH] feat: solve 399. Evaluate Division with unit testing --- README.md | 10 ++- problems/evaluate_division/config.yml | 17 ++++ .../evaluate_division/evaluate_division.cc | 78 +++++++++++++++++++ .../evaluate_division/evaluate_division.h | 6 ++ .../evaluate_division/evaluate_division.py | 43 ++++++++++ .../evaluate_division_test.cc | 42 ++++++++++ .../evaluate_division_test.py | 33 ++++++++ 7 files changed, 227 insertions(+), 2 deletions(-) create mode 100644 problems/evaluate_division/config.yml create mode 100644 problems/evaluate_division/evaluate_division.cc create mode 100644 problems/evaluate_division/evaluate_division.h create mode 100644 problems/evaluate_division/evaluate_division.py create mode 100644 problems/evaluate_division/evaluate_division_test.cc create mode 100644 problems/evaluate_division/evaluate_division_test.py diff --git a/README.md b/README.md index 64e7137..5dcc293 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ ### 📊 Repository Stats [![Last Commit](https://img.shields.io/github/last-commit/mathusanm6/LeetCode?style=for-the-badge&logo=git&logoColor=white&color=blue)](https://github.com/mathusanm6/LeetCode/commits/main) -[![C++ Solutions](https://img.shields.io/badge/C%2B%2B%20Solutions-7-blue?style=for-the-badge&logo=cplusplus&logoColor=white)](https://github.com/mathusanm6/LeetCode/tree/main/problems) -[![Python Solutions](https://img.shields.io/badge/Python%20Solutions-7-blue?style=for-the-badge&logo=python&logoColor=white)](https://github.com/mathusanm6/LeetCode/tree/main/problems) +[![C++ Solutions](https://img.shields.io/badge/C%2B%2B%20Solutions-8-blue?style=for-the-badge&logo=cplusplus&logoColor=white)](https://github.com/mathusanm6/LeetCode/tree/main/problems) +[![Python Solutions](https://img.shields.io/badge/Python%20Solutions-8-blue?style=for-the-badge&logo=python&logoColor=white)](https://github.com/mathusanm6/LeetCode/tree/main/problems) @@ -205,3 +205,9 @@ This repository covers a comprehensive range of algorithmic patterns and data st | # | Title | Solution | Time | Space | Difficulty | Tag | Note | |---|-------|----------|------|-------|------------|-----|------| | 1087 | [Brace Expansion](https://leetcode.com/problems/brace-expansion/) | [Python](./problems/brace_expansion/brace_expansion.py), [C++](./problems/brace_expansion/brace_expansion.cc) | _O(M^K + M log M)_ | _O(M^K)_ | Medium | | M = max choices per brace set, K = number of brace sets. M^K for generating combinations, M log M for sorting. | + +## Graphs + +| # | Title | Solution | Time | Space | Difficulty | Tag | Note | +|---|-------|----------|------|-------|------------|-----|------| +| 399 | [Evaluate Division](https://leetcode.com/problems/evaluate-division/) | [Python](./problems/evaluate_division/evaluate_division.py), [C++](./problems/evaluate_division/evaluate_division.cc) | _O(N + M)_ | _O(N)_ | Medium | | N = number of equations, M = number of queries. | diff --git a/problems/evaluate_division/config.yml b/problems/evaluate_division/config.yml new file mode 100644 index 0000000..9b24067 --- /dev/null +++ b/problems/evaluate_division/config.yml @@ -0,0 +1,17 @@ +problem: + number: 399 + title: "Evaluate Division" + leetcode_url: "https://leetcode.com/problems/evaluate-division/" + difficulty: "medium" + tags: ["Graphs"] + +solutions: + python: "problems/evaluate_division/evaluate_division.py" + cpp: "problems/evaluate_division/evaluate_division.cc" + +complexity: + time: "O(N + M)" + space: "O(N)" + +notes: "N = number of equations, M = number of queries." +readme_link: "" diff --git a/problems/evaluate_division/evaluate_division.cc b/problems/evaluate_division/evaluate_division.cc new file mode 100644 index 0000000..5f34474 --- /dev/null +++ b/problems/evaluate_division/evaluate_division.cc @@ -0,0 +1,78 @@ +#include "evaluate_division.h" + +#include +#include +#include +#include + +using std::string; +using std::unordered_map; +using std::vector; + +namespace { +std::unordered_map parent; +std::unordered_map weight; + +// NOLINTBEGIN(misc-no-recursion) +string find(const string& node, unordered_map& parent, + unordered_map& weight) { + if (parent[node] != node) { + const string originalParent = parent[node]; + parent[node] = find(originalParent, parent, weight); + weight[node] *= weight[originalParent]; + } + return parent[node]; +} +// NOLINTEND(misc-no-recursion) +} // namespace + +vector evaluateDivision(const vector>& equations, + const vector& values, + const vector>& queries) { + // Clear previous state + parent.clear(); + weight.clear(); + + const size_t numEquations = equations.size(); + + for (const auto& equation : equations) { + parent[equation[0]] = equation[0]; + parent[equation[1]] = equation[1]; + weight[equation[0]] = 1.0; + weight[equation[1]] = 1.0; + } + + for (size_t i = 0; i < numEquations; ++i) { + const vector& equation = equations[i]; + const string& numerator = equation[0]; + const string& denominator = equation[1]; + + const string parentNumerator = find(numerator, parent, weight); + const string parentDenominator = find(denominator, parent, weight); + + if (parentNumerator == parentDenominator) { + continue; + } + + parent[parentNumerator] = parentDenominator; + weight[parentNumerator] = values[i] * weight[denominator] / weight[numerator]; + } + + const size_t numQueries = queries.size(); + vector results(numQueries); + + for (size_t i = 0; i < numQueries; ++i) { + const vector& query = queries[i]; + const string& numerator = query[0]; + const string& denominator = query[1]; + + if (!parent.contains(numerator) || !parent.contains(denominator) || + find(numerator, parent, weight) != find(denominator, parent, weight)) { + results[i] = -1.0; + } else { + results[i] = weight[numerator] / weight[denominator]; + } + } + + return results; +} \ No newline at end of file diff --git a/problems/evaluate_division/evaluate_division.h b/problems/evaluate_division/evaluate_division.h new file mode 100644 index 0000000..dd3b245 --- /dev/null +++ b/problems/evaluate_division/evaluate_division.h @@ -0,0 +1,6 @@ +#include +#include + +std::vector evaluateDivision(const std::vector>& equations, + const std::vector& values, + const std::vector>& queries); diff --git a/problems/evaluate_division/evaluate_division.py b/problems/evaluate_division/evaluate_division.py new file mode 100644 index 0000000..0620181 --- /dev/null +++ b/problems/evaluate_division/evaluate_division.py @@ -0,0 +1,43 @@ +from typing import List +from collections import defaultdict + + +def evaluateDivision( + equations: List[List[str]], values: List[float], queries: List[List[str]] +) -> List[float]: + def find(node: str) -> str: + if parent[node] != node: + original_parent = parent[node] + parent[node] = find(original_parent) + weight[node] *= weight[original_parent] + return parent[node] + + weight = defaultdict(lambda: 1.0) + parent = defaultdict(str) + + for numerator, denominator in equations: + parent[numerator] = numerator + parent[denominator] = denominator + + for (numerator, denominator), value in zip(equations, values): + root_numerator = find(numerator) + root_denominator = find(denominator) + + if root_numerator == root_denominator: + continue + + parent[root_numerator] = root_denominator + weight[root_numerator] = value * weight[denominator] / weight[numerator] + + results = [] + for numerator, denominator in queries: + if ( + numerator not in parent + or denominator not in parent + or find(numerator) != find(denominator) + ): + results.append(-1.0) + else: + results.append(weight[numerator] / weight[denominator]) + + return results diff --git a/problems/evaluate_division/evaluate_division_test.cc b/problems/evaluate_division/evaluate_division_test.cc new file mode 100644 index 0000000..5074ac3 --- /dev/null +++ b/problems/evaluate_division/evaluate_division_test.cc @@ -0,0 +1,42 @@ +#include "evaluate_division.h" + +#include +#include +#include + +struct EvaluateDivisionCase { + std::string test_name; + std::vector> equations; + std::vector values; + std::vector> queries; + std::vector expected; +}; + +using EvaluateDivisionTest = ::testing::TestWithParam; + +TEST_P(EvaluateDivisionTest, TestCases) { + const EvaluateDivisionCase &testCase = GetParam(); + const auto result = evaluateDivision(testCase.equations, testCase.values, testCase.queries); + EXPECT_EQ(result, testCase.expected); +} + +INSTANTIATE_TEST_SUITE_P( + EvaluateDivisionTestCases, EvaluateDivisionTest, + ::testing::Values( + EvaluateDivisionCase{ + .test_name = "example1", + .equations = {{"a", "b"}, {"b", "c"}}, + .values = {2.0, 3.0}, + .queries = {{"a", "c"}, {"b", "a"}, {"a", "e"}, {"a", "a"}, {"x", "x"}}, + .expected = {6.00000, 0.50000, -1.00000, 1.00000, -1.00000}}, + EvaluateDivisionCase{.test_name = "example2", + .equations = {{"a", "b"}, {"b", "c"}, {"bc", "cd"}}, + .values = {1.5, 2.5, 5.0}, + .queries = {{"a", "c"}, {"c", "b"}, {"bc", "cd"}, {"cd", "bc"}}, + .expected = {3.75000, 0.40000, 5.00000, 0.20000}}, + EvaluateDivisionCase{.test_name = "example3", + .equations = {{"a", "b"}}, + .values = {0.5}, + .queries = {{"a", "b"}, {"b", "a"}, {"a", "c"}, {"x", "y"}}, + .expected = {0.50000, 2.00000, -1.00000, -1.00000}}), + [](const testing::TestParamInfo &info) { return info.param.test_name; }); diff --git a/problems/evaluate_division/evaluate_division_test.py b/problems/evaluate_division/evaluate_division_test.py new file mode 100644 index 0000000..c50a3cd --- /dev/null +++ b/problems/evaluate_division/evaluate_division_test.py @@ -0,0 +1,33 @@ +"""Test cases for the evaluate_division function.""" + +import pytest + +from evaluate_division import evaluateDivision + + +@pytest.mark.parametrize( + "equations, values, queries, expected", + [ + ( + [["a", "b"], ["b", "c"]], + [2.0, 3.0], + [["a", "c"], ["b", "a"], ["a", "e"], ["a", "a"], ["x", "x"]], + [6.00000, 0.50000, -1.00000, 1.00000, -1.00000], + ), # Example 1 + ( + [["a", "b"], ["b", "c"], ["bc", "cd"]], + [1.5, 2.5, 5.0], + [["a", "c"], ["c", "b"], ["bc", "cd"], ["cd", "bc"]], + [3.75000, 0.40000, 5.00000, 0.20000], + ), # Example 2 + ( + [["a", "b"]], + [0.5], + [["a", "b"], ["b", "a"], ["a", "c"], ["x", "y"]], + [0.50000, 2.00000, -1.00000, -1.00000], + ), # Example 3 + ], + ids=["example_1", "example_2", "example_3"], +) +def test_evaluate_division(equations, values, queries, expected): + assert evaluateDivision(equations, values, queries) == expected