commit 28b59bb2e748d8101a42c94751fea6c3fa037ff4 Author: viktor Date: Tue Apr 1 23:08:30 2025 +0300 finished diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..98e9ddb --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*__cache__* +*__pycache__* +.venv +.pytest_cache \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..8a5421b --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# Nordhealth test + +## Problem + +Given an unsorted array A[]. The task is to print all unique pairs in the unsorted array with equal sum. +Note: Print the result in the format as shown in the below examples. + +Examples: +``` +Input: A[] = { 6, 4, 12, 10, 22, 54, 32, 42, 21, 11} +Output: +Pairs : ( 4, 12) ( 6, 10) have sum : 16 +Pairs : ( 10, 22) ( 21, 11) have sum : 32 +Pairs : ( 12, 21) ( 22, 11) have sum : 33 +Pairs : ( 22, 21) ( 32, 11) have sum : 43 +Pairs : ( 32, 21) ( 42, 11) have sum : 53 +Pairs : ( 12, 42) ( 22, 32) have sum : 54 +Pairs : ( 10, 54) ( 22, 42) have sum : 64 +``` +``` +Input:A[]= { 4, 23, 65, 67, 24, 12, 86} +Output: +Pairs : ( 4, 86) ( 23, 67) have sum : 90 +``` + +## Solution + +Solution in `nordhealth_test/solution.py:51` (print_pairs_with_same_sum()) + +Tests for the inputs are defined it `tests/test_solution.py` + +For package manager I am using poetry, so to activate venv: + - `poetry install` + - `poetry env activate` + +## Running +to run the solution: + - `chmod +x ./nordhealth_test/solution.py` + - `./nordhealth_test/solution.py [path to input file]` + - Example: `./nordhealth_test/solution.py ./inputs/input1.txt` + +To run the tests: + - `pytest tests/` diff --git a/inputs/input1.txt b/inputs/input1.txt new file mode 100644 index 0000000..3315711 --- /dev/null +++ b/inputs/input1.txt @@ -0,0 +1 @@ +6,4,12,10,22,54,32,42,21,11 \ No newline at end of file diff --git a/inputs/input2.txt b/inputs/input2.txt new file mode 100644 index 0000000..e80024a --- /dev/null +++ b/inputs/input2.txt @@ -0,0 +1 @@ +4,23,65,67,24,12,86 \ No newline at end of file diff --git a/nordhealth_test/__init__.py b/nordhealth_test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nordhealth_test/solution.py b/nordhealth_test/solution.py new file mode 100755 index 0000000..164bcff --- /dev/null +++ b/nordhealth_test/solution.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +import sys +from typing import List +from collections import defaultdict + +""" +Given an unsorted array A[]. The task is to print all unique pairs in the unsorted array with equal sum. +Note: Print the result in the format as shown in the below examples. + +Examples: + +Input: A[] = { 6, 4, 12, 10, 22, 54, 32, 42, 21, 11} +Output: +Pairs : ( 4, 12) ( 6, 10) have sum : 16 +Pairs : ( 10, 22) ( 21, 11) have sum : 32 +Pairs : ( 12, 21) ( 22, 11) have sum : 33 +Pairs : ( 22, 21) ( 32, 11) have sum : 43 +Pairs : ( 32, 21) ( 42, 11) have sum : 53 +Pairs : ( 12, 42) ( 22, 32) have sum : 54 +Pairs : ( 10, 54) ( 22, 42) have sum : 64 + +Input:A[]= { 4, 23, 65, 67, 24, 12, 86} +Output: +Pairs : ( 4, 86) ( 23, 67) have sum : 90 +""" +def main() -> None: + argc = len(sys.argv) + argv = sys.argv + if argc != 2: + raise ValueError("Invalid input. You need to provide a single path to the input file. \n Ex.: `./solution.py ./input1.txt`") + + # Read input from the file and process the data + try: + input_data = read_input(argv[1]) + print_pairs_with_same_sum(input_data) + except ValueError as e: + print(f"Error: {e}") + sys.exit(1) + + +def read_input(input_file: str) -> List[int]: + """ Reads the input file and returns a list of integers. """ + try: + with open(input_file, "r") as file: + lst = file.readline().split(',') + parsed = [int(x.strip()) for x in lst] + return parsed + except Exception as e: + raise ValueError(f"Failed to read or parse the file: {e}") + +def print_pairs_with_same_sum(arr: List[int]) -> None: + """ + Solution to the problem. Since performance is required it's in one method, but could be separated: + 1). Calculate the sums into a hashmap and add the pairs + 2). print the result + """ + + # Not a valid array + if len(arr) < 2: + print("No pairs can be formed from the input file.") + + sum_map = defaultdict(list) + + # Check for duplicate pairs. + seen_pairs = set() + + n = len(arr) + + for i in range(n): + # Time Complexity (O(n^2)), as we have n(n-1)/2 for the second loop. + for j in range(i + 1, n): + current_sum = arr[i] + arr[j] + pair = tuple((arr[i], arr[j])) + + if pair not in seen_pairs: + # This will output nothing for [2,2,2,2]. Depending on the requirements we can store the pair using indices, + # then the result would be: + # (A[0],A[1]) (A[0],A[2]) (A[0],A[3]) (A[1],A[2]) (A[1],A[3]) (A[2],A[3]) - sum: 4 + # And we will not need the seen_pairs set. + sum_map[current_sum].append(pair) + seen_pairs.add(pair) + + # This is required to follow the example output, as it is presented as sorted by sum in the task description. + # We can skip the sorting if this is not really part of the requiremetns. + for sum_val in sorted(sum_map.keys()): + pairs = sum_map[sum_val] + + if len(pairs) > 1: + pairs.sort() + + print(f"Pairs : ", end="") + for pair in pairs: + print(f"( {pair[0]}, {pair[1]})", end=" ") + print(f"have sum : {sum_val}") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..80c4800 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,80 @@ +# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] +markers = "sys_platform == \"win32\"" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, + {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, +] + +[[package]] +name = "packaging" +version = "24.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pytest" +version = "8.3.5" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"}, + {file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2" + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[metadata] +lock-version = "2.1" +python-versions = ">=3.13" +content-hash = "8ef1d528c82ebf4a734833eed980b6b031e288bf5563655b512f75969b90d60a" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..a9ff475 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,20 @@ +[project] +name = "nordhealth-test" +version = "0.1.0" +description = "" +authors = [ + {name = "viktor",email = "me@vpanteleev.com"} +] +readme = "README.md" +requires-python = ">=3.13" +dependencies = [ +] + +[tool.poetry] + +[tool.poetry.group.dev.dependencies] +pytest = "^8.3.5" + +[build-system] +requires = ["poetry-core>=2.0.0,<3.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/tests/test_solution.py b/tests/test_solution.py new file mode 100644 index 0000000..c38c47d --- /dev/null +++ b/tests/test_solution.py @@ -0,0 +1,55 @@ +import sys +from io import StringIO +from nordhealth_test.solution import print_pairs_with_same_sum + +def capture_output(func, *args, **kwargs): + captured_output = StringIO() + sys.stdout = captured_output + func(*args, **kwargs) + sys.stdout = sys.__stdout__ + return captured_output.getvalue() + +# Test case Input 1: [6, 4, 12, 10, 22, 54, 32, 42, 21, 11] +def test_input_1(): + arr = [6, 4, 12, 10, 22, 54, 32, 42, 21, 11] + expected_output = ( + "Pairs : ( 4, 12) ( 6, 10) have sum : 16\n" + "Pairs : ( 10, 22) ( 21, 11) have sum : 32\n" + "Pairs : ( 12, 21) ( 22, 11) have sum : 33\n" + "Pairs : ( 22, 21) ( 32, 11) have sum : 43\n" + "Pairs : ( 32, 21) ( 42, 11) have sum : 53\n" + "Pairs : ( 12, 42) ( 22, 32) have sum : 54\n" + "Pairs : ( 10, 54) ( 22, 42) have sum : 64\n" + ) + result = capture_output(print_pairs_with_same_sum, arr) + assert result == expected_output + +# Test case Input 2: [4, 23, 65, 67, 24, 12, 86] +def test_input_2(): + arr = [4, 23, 65, 67, 24, 12, 86] + expected_output = ( + "Pairs : ( 4, 86) ( 23, 67) have sum : 90\n" + ) + result = capture_output(print_pairs_with_same_sum, arr) + assert result == expected_output + +# Test case: Array with no valid pairs +def test_no_valid_pairs(): + arr = [1, 2, 3, 5] + expected_output = "" + result = capture_output(print_pairs_with_same_sum, arr) + assert result == expected_output + +# Test case: Array with exactly one element +def test_single_element(): + arr = [10] + expected_output = "No pairs can be formed from the input file.\n" + result = capture_output(print_pairs_with_same_sum, arr) + assert result == expected_output + +# Test case: Empty array +def test_empty_array(): + arr = [] + expected_output = "No pairs can be formed from the input file.\n" + result = capture_output(print_pairs_with_same_sum, arr) + assert result == expected_output \ No newline at end of file